而关闭线程通常又有以下两种:
- 立即关闭:执行关闭方法后不管现在线程池的运行状况,直接一刀切全部停掉,这样会导致任务丢失。
- 不接受新的任务,同时等待现有任务执行完毕后退出线程池。
立即关闭
我们先来看第一种立即关闭
:
/** * 立即关闭线程池,会造成任务丢失 */ public void shutDownNow() { isShutDown.set(true); tryClose(false); } /** * 关闭线程池 * * @param isTry true 尝试关闭 --> 会等待所有任务执行完毕 * false 立即关闭线程池--> 任务有丢失的可能 */ private void tryClose(boolean isTry) { if (!isTry) { closeAllTask(); } else { if (isShutDown.get() && totalTask.get() == 0) { closeAllTask(); } } } /** * 关闭所有任务 */ private void closeAllTask() { for (Worker worker : workers) { //LOGGER.info("开始关闭"); worker.close(); } } public void close() { thread.interrupt(); }
很容易看出,最终就是遍历线程池里所有的 worker
线程挨个执行他们的中断函数。
我们来测试一下:
可以发现后面丢进去的三个任务其实是没有被执行的。
完事后关闭
而正常关闭则不一样:
/** * 任务执行完毕后关闭线程池 */ public void shutdown() { isShutDown.set(true); tryClose(true); }
他会在这里多了一个判断,需要所有任务都执行完毕之后才会去中断线程。
同时在线程需要回收时都会尝试关闭线程:
来看看实际效果:
回收线程
上文或多或少提到了线程回收的事情,其实总结就是以下两点:
- 一旦执行了
shutdown/shutdownNow
方法都会将线程池的状态置为关闭状态,这样只要worker
线程尝试从队列里获取任务时就会直接返回空,导致worker
线程被回收。
- 一旦线程池大小超过了核心线程数就会使用保活时间来从队列里获取任务,所以一旦获取不到返回
null
时就会触发回收。
但如果我们的队列足够大,导致线程数都不会超过核心线程数,这样是不会触发回收的。
比如这里我将队列大小调为 10 ,这样任务就会累计在队列里,不会创建五个 worker
线程。
所以一直都是 Thread-1~3
这三个线程在反复调度任务。
总结
本次实现了线程池里大部分核心功能,我相信只要看完并动手敲一遍一定会对线程池有不一样的理解。
结合目前的内容来总结下:
- 线程池、队列大小要设计的合理,尽量的让任务从队列中获取执行。
- 慎用
shutdownNow()
方法关闭线程池,会导致任务丢失(除非业务允许)。
- 如果任务多,线程执行时间短可以调大
keepalive
值,使得线程尽量不被回收从而可以复用线程。
同时下次会分享一些线程池的新特性,如:
- 执行带有返回值的线程。
- 异常处理怎么办?
- 所有任务执行完怎么通知我?
本文所有源码: