【原创】 Gorhaf 2019-05-23 16:09:51
1.温故知新
前面在《“全栈2019”Java线程进阶第十六章:有执行结果的任务Callable》一章中介绍了有执行结果的任务Callable。
在《“全栈2019”Java线程进阶第十七章:表示异步计算的结果Future》一章中介绍了表示异步计算的结果Future。
在《“全栈2019”Java线程进阶第十八章:可取消的异步任务FutureTask》一章中介绍了可取消的异步任务FutureTask。
本章介绍线程池实现类ThreadPoolExecutor。
2.线程池实现类ThreadPoolExecutor
之前我们在《“全栈2019”Java线程进阶第二章:创建线程池的三种方式》一章中介绍了线程池创建的三种方式。
分别是以下三种:
- Executors.newFixedThreadPool(int nThreads);// 创建固定大小的线程池
- Executors.newSingleThreadExecutor();// 创建只有一个线程的线程池
- Executors.newCachedThreadPool();// 创建可缓存的线程池
从图中我们可以看出,这三个方法里面都创建的是ThreadPoolExecutor对象:
那么这个ThreadPoolExecutor类是干什么的呢?
ThreadPoolExecutor 是线程池的实现类。
为何叫“线程池的实现类”?
因为线程池有许多规范和规则,而这些规范和规则都是通过接口来定义的,所以最终定义出来的线程池类,必然是实现了这些接口,所以说ThreadPoolExecutor是线程池的实现类。
早在《“全栈2019”Java线程进阶第十四章:线程池的核心执行器Executor》一章的第5小节介绍了“线程池的分工与合作”:
- Executors 负责创建线程池。
- Executor 负责提交Runnable任务。
- ExecutorService 扩展自Executor,新增提交Callable任务、关闭线程池等功能。
- AbstractExecutorService实现ExecutorService部分功能。
- ThreadPoolExecutor继承自AbstractExecutorService,线程池的实现类。
线程池ThreadPoolExecutor的继承关系图:
3.ThreadPoolExecutor构造方法
ThreadPoolExecutor类有4个构造方法:
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
- ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
看起来构造方法很多,方法中的参数也很多,不好入手。
其实,它们是有递进关系的。
例如:
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)构造方法内部调用的是ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)构造方法:
而ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)构造方法则调用的是ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)构造方法:
以此类推,ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)构造方法调用的是ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)构造方法:
综上所述,我们只需搞明白ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)构造方法即可。
构造方法的参数
int corePoolSize:线程池中核心线程数。
int maximumPoolSize:线程池中最大线程数。
long keepAliveTime:当线程数大于核心线程数时,这是多余空闲线程在终止之前等待新任务的最长时间,超时时空闲线程则被销毁。
TimeUnit unit:keepAliveTime参数的时间单位。
BlockingQueue<Runnable> workQueue:在执行任务之前用于保存任务的队列。 此队列将仅保存execute方法提交的Runnable任务。
ThreadFactory threadFactory:线程池创建线程时所用的线程工厂。
RejectedExecutionHandler handler:由于线程池中已没有空闲线程或任务队列已满而拒绝任务的处理程序。
理解线程池的核心线程数和最大线程数
核心线程数就好比店里的核心员工。
最大线程数就好比店里的核心员工+兼职员工。
在线程池中corePoolSize表示核心线程数;maximumPoolSize表示最大线程数。
例如:
一个店里有5个核心员工,当店里不忙的时候也不会被解聘。当店里比较忙,人手不够的情况下,店里会再招5个兼职员工,因为只有这段时间比较忙,为了节约成本,招几个兼职员工来帮忙比较划算,等忙过这段时间,兼职员工就被解聘。
换成线程池也一样,核心线程不会被销毁,即使线程空闲的情况下也不会被销毁。当任务比较多的情况下,线程池会再多创建几个线程,新建的线程数+核心线程数<=最大线程数。同样的,当任务不多时,除核心线程以外的会被销毁。
理解keepAliveTime参数
keepAliveTime参数就是除核心线程以外的空闲线程(注意:是空闲线程,正在工作的线程不会被销毁),它们必须在这段时间内有任务执行,否则就会被销毁。
keepAliveTime还有一个配合使用的TimeUnit unit参数,unit指定时间的单位。
例如,keepAliveTime写的是10,若unit指定是的TimeUnit.SECONDS,那就是10秒;若unit指定的是TimeUnit.DAYS,那就是10天。
还是以员工为例,核心员工不会被解聘,店里10天内若没有很忙的话,10天后兼职员工就会被解聘。
换成线程池,就是核心线程不会被销毁,即使它们是空闲的,但是非核心线程就不一样了,它们会在keepAliveTime时间后销毁,如果它们还没有任务执行的话。
理解任务队列workQueue
线程池中的线程好比店里的员工,前来购物的客人好比任务,员工与客人是1对1服务的,但也会出现客人多于员工的情况,这时需要一部分客人稍等一下,在旁边坐一会,等有空闲员工的时候再来服务他们。
换成线程池,任务与线程是一对一的关系,一个线程执行一个任务,当任务数多于线程数时,一部分任务就会被加入到任务队列中,等待被执行。
在线程池中,BlockingQueue<Runnable> workQueue就是在执行任务之前用于保存任务的队列。
注意:此队列将仅保存execute方法提交的Runnable任务。
当然了,上一章我们学习过FutureTask和RunnableFuture就把Callable任务和Runnable任务结合在一起,所以这里Callable任务也可以存入到任务队列中。
理解线程工厂threadFactory
线程池中需要频繁地创建和销毁线程,在《“全栈2019”Java线程进阶第四章:线程池中默认的线程工厂》一章中我们理解到线程池中的线程拥有统一设定,比如统一的名称格式,新创建的线程是否为后台线程,以及它们的优先级是多少等等。已经不能通过单纯的方法来创建了,我们对线程有不同的需求,需要创建出不同的线程,所以,制定统一的规则是很有必要的。
ThreadFactory是一个接口:
若想创建满足自己要求的线程实现ThreadFactory接口即可。
在线程池中,也有一个系统实现的默认线程工厂:
这个默认的线程工厂没有定义在ThreadPoolExecutor类中,而是定义在Executors类中。
理解拒绝任务处理程序
当店里人满为患的时候,我们会为了客人的安全着想拒绝再让店外的顾客进店,既然拒绝了顾客进店,那么肯定要给一个说辞,可以以安全为由,也可以是其他什么理由,反正总得给个理由,这就是所谓的拒绝处理程序。
换成线程池,提交到线程池的任务也有可能被拒绝,原因也各有不同,比如线程池中已没有空闲的线程能够执行新提交的任务,且任务队列已满,这时,需要拒绝那些还在往线程池中提交的任务。拒绝新提交进来的任务需要给它们一个说辞,那么这个说辞就由RejectedExecutionHandler完成。
单词RejectedExecutionHandler可以分为Rejected、Execution、Handler三个单词来看:
Rejected:拒绝。
Execution:执行。
Handler:处理程序。
RejectedExecutionHandler和处理异常差不多,大家也可以理解为任务在提交的时候发生异常,线程池怎么告知我们这个异常呢?那就是通过RejectedExecutionHandler来告知我们。
在线程池ThreadPoolExecutor类中有4个内置的拒绝任务处理程序:
分别是:
- CallerRunsPolicy // 直接调用任务的run()方法。
- AbortPolicy // 丢弃任务并抛RejectedExecutionException。
- DiscardPolicy // 丢弃任务,但不抛异常。
- DiscardOldestPolicy // 抛弃任务队列中等待最久的任务,然后把当前任务加入队列中。
默认的拒绝任务处理程序是:
即AbortPolicy,丢弃任务并抛RejectedExecutionException。
4.线程池工具类Executors是怎么创建线程池的?
Executors是一个帮我们快速创建线程池的工具类。
它里面有好几个创建线程池的方法,这里只列出常用的三个:
- Executors.newSingleThreadExecutor();// 创建只有一个线程的线程池
- Executors.newFixedThreadPool(int nThreads);// 创建固定大小的线程池
- Executors.newCachedThreadPool();// 创建可缓存的线程池
这三个方法内部具体调用情况如下:
- new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
- new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())
- new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
可以看到的是:
- 创建只有一个线程的线程池的方法核心线程数为1,最大线程数也为1,空闲非核心线程超时时间为0毫秒,任务队列采用的是LinkedBlockingQueue;
- 创建固定大小的线程池的方法核心线程数为自定义数,最大线程数也为自定义数,空闲非核心线程超时时间为0毫秒,任务队列采用的是LinkedBlockingQueue;
- 创建可缓存的线程池的方法核心线程数为0,最大线程数为Integer.MAX_VALUE,空闲非核心线程超时时间为60秒,任务队列采用的是SynchronousQueue。
为什么只有一个线程的线程池和固定大小的线程池它们的空闲非核心线程超时时间为0毫秒?
因为它们的核心线程允许超时,所以即使超时时间设置为0毫秒它们也不会被销毁。
5.使用ThreadPoolExecutor创建线程池
下面写一个使用ThreadPoolExecutor来创建一个线程池,并执行任务的完整例子。
准备Callable任务:
然后,创建ThreadPoolExecutor对象:
核心线程数为1,最大线程数也为1,空闲非核心线程超时时间为0秒,任务队列暂时没有创建出来。
接着,创建出任务队列:
然后,向线程池中提交任务:
接着,获取任务执行完毕后的结果:
然后,输出任务执行完毕的结果:
最后,关闭线程池:
例子书写完毕。
运行程序,执行结果:
从运行结果来看,符合预期。
和我们通过线程池工具类Executors创建出来的线程池功能一摸一样。
但是,ThreadPoolExecutor的功能要比ExecutorService功能多。
6.ThreadPoolExecutor线程池功能
ThreadPoolExecutor已知的有29个功能:
功能的作用如下:
- void execute(Runnable command)方法的作用是执行Runnable任务。
- void shutdown()方法的作用是执行关闭线程池操作后,正在执行的任务和未执行的任务都将继续被执行完,且线程池不再接收新的任务。
- List<Runnable> shutdownNow()方法的作用是执行关闭线程池操作后,返回还未开始执行的任务并尝试停止所有正在执行的任务,尝试停止的方式就是调用正在执行任务的线程的interrupt()方法。另外不再接收新的任务。
- boolean isShutdown()方法的作用是当线程池已经关闭返回true,否则返回false。
- boolean isTerminating()方法的作用是当线程池正在关闭时返回true,否则返回false。
- boolean isTerminated()方法的作用是当线程池已经关闭且线程池中的任务全已执行完毕返回true,否则返回false。
- boolean awaitTermination(long timeout, TimeUnit unit)方法的作用是当线程池在指定时间内关闭且线程池中的任务全部已经执行完毕时返回true,否则返回false。
- void setThreadFactory(ThreadFactory threadFactory)方法的作用是设置新的线程工厂。
- ThreadFactory getThreadFactory()方法的作用是获取线程工厂。
- void setRejectedExecutionHandler(RejectedExecutionHandler handler)方法的作用是设置拒绝任务执行处理程序。
- RejectedExecutionHandler getRejectedExecutionHandler()方法的作用是获取拒绝任务执行处理程序。
- void setCorePoolSize(int corePoolSize)方法的作用是设置线程池核心线程数。
- int getCorePoolSize()方法的作用是获取线程池核心线程数。
- boolean prestartCoreThread()方法的作用是启动空闲的核心线程,使其空转,如果线程启动成功返回true;如果所有核心线程都已启动则返回false。
- int prestartAllCoreThreads()方法的作用是启动所有空闲的核心线程,使其空转,方法返回启动的线程数。
- boolean allowsCoreThreadTimeOut()方法的作用是当允许核心线程超时被销毁时返回true,否则返回false。
- void allowCoreThreadTimeOut(boolean value)方法的作用是设置是否允许核心线程超时,若为true,核心线程超时会被销毁;若为false,核心线程不会因超时而被销毁。
- void setMaximumPoolSize(int maximumPoolSize)方法的作用是线程池最大线程数。
- int getMaximumPoolSize()方法的作用是获取线程池最大线程数。
- void setKeepAliveTime(long time, TimeUnit unit)方法的作用是设置线程保持活动时间,这是线程在终止之前可以保持空闲的时间量。
- long getKeepAliveTime(TimeUnit unit)方法的作用是获取设置线程保持活动时间。
- BlockingQueue<Runnable> getQueue()方法的作用是获取线程池中的任务队列。
- boolean remove(Runnable task)方法的作用是删除正在任务队列中的Runnable任务。
- void purge()方法的作用是清除已经被取消的任务。
- int getPoolSize()方法的作用是获取当前线程数。
- int getActiveCount()方法的作用是获取正在执行任务的线程数。
- int getLargestPoolSize()方法的作用是获取线程池中曾经出现过的最大线程数。
- long getTaskCount()方法的作用是获取已经安排执行的任务数。因为任务和线程的状态可能在计算期间动态改变,所以返回的值仅是近似值。
- long getCompletedTaskCount()方法的作用是获取已完成执行的任务数。因为任务和线程的状态可能在计算期间动态改变,所以返回的值仅是近似值。
线程池ThreadPoolExecutor的功能的详细使用后续章节再介绍。
最后,希望大家可以把这个例子照着写一遍,然后再自己默写一遍,方便以后碰到类似的面试题可以轻松应对。
祝大家编码愉快!
GitHub
本章程序GitHub地址:https://github.com/gorhaf/Java2019/tree/master/Thread/ThreadPoolExecutor
总结
- ThreadPoolExecutor是线程池的实现类。
- void execute(Runnable command)方法的作用是执行Runnable任务。
- void shutdown()方法的作用是执行关闭线程池操作后,正在执行的任务和未执行的任务都将继续被执行完,且线程池不再接收新的任务。
- List<Runnable> shutdownNow()方法的作用是执行关闭线程池操作后,返回还未开始执行的任务并尝试停止所有正在执行的任务,尝试停止的方式就是调用正在执行任务的线程的interrupt()方法。另外不再接收新的任务。
- boolean isShutdown()方法的作用是当线程池已经关闭返回true,否则返回false。
- boolean isTerminating()方法的作用是当线程池正在关闭时返回true,否则返回false。
- boolean isTerminated()方法的作用是当线程池已经关闭且线程池中的任务全已执行完毕返回true,否则返回false。
- boolean awaitTermination(long timeout, TimeUnit unit)方法的作用是当线程池在指定时间内关闭且线程池中的任务全部已经执行完毕时返回true,否则返回false。
- void setThreadFactory(ThreadFactory threadFactory)方法的作用是设置新的线程工厂。
- ThreadFactory getThreadFactory()方法的作用是获取线程工厂。
- void setRejectedExecutionHandler(RejectedExecutionHandler handler)方法的作用是设置拒绝任务执行处理程序。
- RejectedExecutionHandler getRejectedExecutionHandler()方法的作用是获取拒绝任务执行处理程序。
- void setCorePoolSize(int corePoolSize)方法的作用是设置线程池核心线程数。
- int getCorePoolSize()方法的作用是获取线程池核心线程数。
- boolean prestartCoreThread()方法的作用是启动空闲的核心线程,使其空转,如果线程启动成功返回true;如果所有核心线程都已启动则返回false。
- int prestartAllCoreThreads()方法的作用是启动所有空闲的核心线程,使其空转,方法返回启动的线程数。
- boolean allowsCoreThreadTimeOut()方法的作用是当允许核心线程超时被销毁时返回true,否则返回false。
- void allowCoreThreadTimeOut(boolean value)方法的作用是设置是否允许核心线程超时,若为true,核心线程超时会被销毁;若为false,核心线程不会因超时而被销毁。
- void setMaximumPoolSize(int maximumPoolSize)方法的作用是线程池最大线程数。
- int getMaximumPoolSize()方法的作用是获取线程池最大线程数。
- void setKeepAliveTime(long time, TimeUnit unit)方法的作用是设置线程保持活动时间,这是线程在终止之前可以保持空闲的时间量。
- long getKeepAliveTime(TimeUnit unit)方法的作用是获取设置线程保持活动时间。
- BlockingQueue<Runnable> getQueue()方法的作用是获取线程池中的任务队列。
- boolean remove(Runnable task)方法的作用是删除正在任务队列中的Runnable任务。
- void purge()方法的作用是清除已经被取消的任务。
- int getPoolSize()方法的作用是获取当前线程数。
- int getActiveCount()方法的作用是获取正在执行任务的线程数。
- int getLargestPoolSize()方法的作用是获取线程池中曾经出现过的最大线程数。
- long getTaskCount()方法的作用是获取已经安排执行的任务数。因为任务和线程的状态可能在计算期间动态改变,所以返回的值仅是近似值。
- long getCompletedTaskCount()方法的作用是获取已完成执行的任务数。因为任务和线程的状态可能在计算期间动态改变,所以返回的值仅是近似值。
至此,Java中线程池ThreadPoolExecutor相关内容讲解先告一段落,更多内容请持续关注。
答疑
如果大家有问题或想了解更多前沿技术,请在下方留言或评论,我会为大家解答。
上一章
“全栈2019”Java线程进阶第十八章:可取消的异步任务FutureTask
下一章
“全栈2019”Java线程进阶第二十章:什么是线程池核心线程数?
学习小组
加入同步学习小组,共同交流与进步。
方式一:欢迎加入“全栈工程师”编程圈子,与圈友一起交流讨论。
(此处已添加圈子卡片,请到今日头条客户端查看)
方式二:关注头条号Gorhaf,私信“Java学习小组”。
方式三:关注公众号Gorhaf,回复“Java学习小组”。
全栈工程师学习计划
版权声明
原创不易,未经允许不得转载!