上篇文章讲了下线程的创立及一些罕用的办法,然而在应用的时候,大多数是采纳了线程池来治理线程的创立,运行,销毁等过程。本篇将着重讲线程池的根底内容,包含通过线程池创立线程,线程池的根本信息等。
创立线程
后期筹备
本大节所有代码都是在CreateThreadByPool
类上,该类还有一个外部类MyThread
实现了Runnable
接口。
首先先把根本的代码给写进去
public class CreateThreadByPool { public static void main(String[] args) { }}class MyThread implements Runnable { @Override public void run() { System.out.println(Thread.currentThread().getName() + " processing"); process(); System.out.println(Thread.currentThread().getName() + " end"); } private void process() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public String toString() { return String.format("MyThread{%s}", Thread.currentThread().getName()); }}
先来大略回顾一下,当咱们想创立10个线程的时候的代码一般形式是怎么的
private static void createThreadByNormalWay() { for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); Thread thread = new Thread(myThread); thread.start(); }}
在能看到的代码中,是应用了start()
本人间接开启了线程,然而如果用线程池形式来呢
通过Executors
第一种创立线程池的办法是通过Executors
类的静态方法来构建,通过这种形式总共能够创立4种线程池。
并且能够发现返回是ExecutorService
,所以还要承受返回值,最初通过execute
来启动线程
private static void createThreadByPool() { ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); executorService.execute(myThread); }}
先不论底层是如何实现的,至多代码上是把线程交给了线程池来执行,这样可能保障线程可能对立治理。
简略的比喻就是前者是要你本人去找班长签到,后者是班长对立治理这整个班的签到。在main函数中调用看看一般办法和通过线程池创立的线程有什么区别
能够很显著的看到有以下几点区别
- 线程的名字都不一样
- 并且一般形式是创立了10个线程,而后者只是创立了5个线程(是由咱们本人设定的)
- 前者基本上是10个线程都是同时解决,后者是最多只能解决5个线程,须要等线程执行完有闲暇能力解决其它线程。
通过ThreadPoolExecutor
除了应用Executors.newFixedThreadPool()
创立线程池,还能够通过new ThreadPoolExecutor()
,这里可能有的小伙伴会迷糊了,怎么下面放回的类是ExecutorService
,当初返回的又是ThreadPoolExecutor
,其实两者是同一个货色。
能够看到ThreadExecutorPool
是继承了 AbstractExecutorService
,而后者是实现了ExecutorService
。通过该办法创立的线程池的代码如下
能够先这样运行体验下,至于说构造函数外面不同参数的含意,在前面的篇幅中会说到,到时候再返回来看即可。
private static void createThreadByThreadPoolExecutor() { ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()); for (int i = 0; i < 10; i++) { MyThread myThread = new MyThread(); executor.execute(myThread); }}
看下运行后果
输入后果没啥好讲的,然而如果仔细的小伙伴在上一个gif就会发现,通过线程池来启动线程的形式,程序并没有退出,会始终运行。这是因为咱们没有shutdown
线程池。
两者区别
回过头来看看Executors.静态方法
这种办法来创立线程池的源码
能够看到其实更深一层还是应用了new ThreadPoolExecutor()
,只不过咱们本人能定制的构造函数的参数变得极其少,这时候必定有小伙伴疑难了,那为什么不间接都用new ThreadPoolExecutor()
呢?
《阿里java开发手册》 嵩山版明确规定了两点,一是线程资源必须通过线程池提供,不容许自行显式创立线程;二是线程池不容许应用Executors去创立,而是通过ThreadPoolExecutor的形式去创立。
着重看第二点强制通过ThreadPoolExecutor的形式来创立线程,起因在上面也有,来看看FixedThreadPool和SingleThreadPool的源码
其它的不论,能够看到两者调用构造函数中的队列都是LinkedBlockingQueue
,这个队列是无边界的,所以有了容许申请长度为Integer.MAX_VALUE
,会沉积大量的申请 ,从而导致OOM。
再来看看CachedThreadPool的源码
留神这里构造函数的第二个参数是线程池最大线程数,它设置成了Integer.MAX_VALUE
,这就可能会创立大量的线程,从而导致OOM。
线程池信息
ThreadPoolExecutor
下面也能够看到,创立线程池最重要也是最应该应用的办法的是new ThreadPoolExecutor()
,接下来把重点放在ThreadPoolExecutor
这个类下面
这个是类中的所有的属性,接下来再看看构造函数
有4种,然而归根结底只有以下这一种构造函数,讲下这些参数的意义,而后大家就能够回头看下上一大节的例子。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { //省略实现}
corePoolSize
:外围线程数,大白话就是可能工作的线程数量maximumPoolSize
:最大线程数,就是这个线程池能包容线程的数量keepAliveTime
:存活工夫,当线程池中的线程数量大于外围线程数的时候,如果时候没有工作提交,外围线程池外的线程不会立刻被销毁,而是会期待,直到期待的工夫超过了这个字段才会被回收销毁unit
:存活工夫的单位workQueue
:工作队列,就是在线程开始被调用前,就是存在这个队列中threadFactory
:线程工厂,执行程序创立新线程时应用的工厂handler
:回绝策略,当达到线程边界和队列容量而采取的回绝策略
对于这个回绝策略,简略说下,有四种实现。
实现RejectedExecutionHandler
接口就能实现本人的回绝策略
监控线程
上面就来简略实现一个本人的回绝策略,并且来看下上述类中属性的信息
首先须要一个监控线程类
class MonitorThread implements Runnable { //注入一个线程池 private ThreadPoolExecutor executor; public MonitorThread(ThreadPoolExecutor executor) { this.executor = executor; } private boolean monitor = true; public void stopMonitor() { monitor = false; } @Override public void run() { //监控始终运行,每3s输入一次状态 while (monitor) { //次要逻辑是监控线程池的状态 System.out.println( String.format("[monitor] [%d/%d] Active: %d, Completed: %d, Task: %d, isShutdown: %s, isTerminated: %s, rejectedExecutionHandler: %s", this.executor.getPoolSize(), this.executor.getCorePoolSize(), this.executor.getActiveCount(), this.executor.getCompletedTaskCount(), this.executor.getTaskCount(), this.executor.isShutdown(), this.executor.isTerminated(), this.executor.getRejectedExecutionHandler())); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } }}
同时实现自定义的回绝策略
其实这还是没有对r解决,回绝了就回绝了,只是打印进去,然而并没有实质性地解决
class MyRejectedExecutionHandler implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("task is rejected"); }}
接下来就是public类TheradPoolInfo
,留神工作线程采纳的是上一大节的MyThread
类
public class ThreadPoolInfo { public static void main(String[] args) throws InterruptedException { //新建了一个线程池,外围线程数是3,最大线程数是5,30s //队列是ArrayBlockingQueue,并且大小边界是3,回绝策略自定义输入一句话 ThreadPoolExecutor executor = new ThreadPoolExecutor(3,5, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3), new MyRejectedExecutionHandler()); //开启监控线程 MonitorThread monitorThread = new MonitorThread(executor); new Thread(monitorThread).start(); //开启工作线程 for (int i = 0; i < 10; i++) { executor.execute(new MyThread()); } //敞开线程池和监控线程 Thread.sleep(12000); executor.shutdown(); Thread.sleep(3000); monitorThread.stopMonitor(); }}
预期后果: 通过构造函数能够晓得,预期是有3个外围线程执行工作,会回绝2个线程,实现8个工作(最大线程数是5,队列长度是3,具体会在下一篇文章中讲)。
能够看到后果和预期的一样
创作不易,如果对你有帮忙,欢送点赞,珍藏和分享啦!
上面是集体公众号,有趣味的能够关注一下,说不定就是你的宝藏公众号哦,根本2,3天1更技术文章!!!