前言:
今天看官们对“java线程池线程回收”大概比较讲究,小伙伴们都想要分析一些“java线程池线程回收”的相关资讯。那么小编在网摘上收集了一些有关“java线程池线程回收””的相关资讯,希望同学们能喜欢,你们快快来学习一下吧!“全文共计2350字17图,预计阅读时间13分钟
大家好,我是tin,这是我的第4篇原创文章
1
来唠嗑唠嗑题外话
配图和本文内容无关,之所以留此配图是因为最近自己作为万年windows系统用户终于尝试买入了人生第一台mac,也就是今年刚出来,大家都比较关心、讨论也满天飞的mac M1!此文作为使用mac输出的第一篇技术文章,谨此纪念。
感觉怎么样呢?作为一个完全mac新用户,主要的体会是这样的:
1. 续航真真长!自己亲测,17-18小时毫无压力。
2. 很不习惯。包括了macOS、快捷键、触摸板、文件管理,到目前为止我还没弄明白control、option、command这三个键的区别。
3. 整机质感很好。最想说的就是屏幕,很细、很腻、很柔,非常有一种舒服感、质感,大体有些「遇见即要护你终生」的感觉。
至于网上一直讨论的兼容性问题,目前还没遇到,因为我本机用得最多的是浏览器、微信、IDEA,这些都能正常运行。
好了,回归正题。
2
究竟谁负责回收线程池空闲线程?
这个问题的来源也是比较巧,曾经跟同事聊天,说怎么去考量候选人简历上的“精通Java线程池”,考虑出发点一是不能网上出现过的,因为可以背答案,二是要能体现候选人真的熟线程池,起码源码看过(非熟悉使用Java线程池)。
一次面试的时候,同事突然想到的这个问题。于是今天我就把这个问题作为本文分析线程池的出发点了。
首先,这个问题常见的答案有:
1. 线程池回收的 ---等于没答
2. 会单独有一个定时任务回收空闲线程 ---完全错误
正确答案是这样的:
超过corePoolSize的空闲线程由线程池回收,线程池Worker启动跑第一个任务之后就一直循环遍历线程池任务队列,超过指定超时时间获取不到任务就remove Worker,最后由垃圾回收器回收。
这里面有两个概念想重新说一下,一个是Worker,另一个是任务队列。Worker是线程池ThreadPoolExecutor的一个内部类,其有一个成员变量thread(线程),所以我们可以把一个Worker假以理解为一个线程。
任务队列是BlockingQueue,都说精通线程池了,如果熟悉这个队列的话,能不熟悉它的几个方法吗?线程池正是利用poll方法的超时时间来决定要不要回收空闲线程的。
boolean poll(E e, long timeout, TimeUnit unit)
3
源码一窥究竟
讲完答案,那就一起看看怎么举证它吧,先把测试用例写起来:
为方便复制,我把源码粘贴一份出来:
/** * title: ThreadPoolExecutorTest * <p> * description: 线程池测试类 * * @author tin on 2021/1/1 下午12:07 */public class ThreadPoolExecutorTest {public static void main(String[] args) throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); BlockingQueue<Runnable> queue = new LinkedBlockingDeque<Runnable>(2); ThreadFactory threadFactory = new MyThreadFactory("@tin的线程池"); RejectedExecutionHandler rejectedExecutionHandler = new MyRejectedExecutionHandler();//① 定义一个线程池,maximumPoolSize=2,队列长度=2 ThreadPoolExecutor executorService = new ThreadPoolExecutor(1,2,2, TimeUnit.SECONDS, queue, threadFactory, rejectedExecutionHandler);//② 执行任务,会启动2个线程处理任务 Runnable task = new MyTask();for (int i = 0; i < 4; i++) { executorService.execute(task); }//③ 经过以下sleep步骤活线程数变为1for (int i = 0; i < 4; i++) { Thread.sleep(1000); System.out.println("corePoolSize:" + executorService.getPoolSize()); }//④ 重新执行任务,此时一个线程不够使用,会重新生成新线程处理任务for (int i = 0; i < 4; i++) { executorService.execute(task); }//⑤ 重新经过以下sleep步骤活线程数重新变为1for (int i = 0; i < 4; i++) { Thread.sleep(1000); System.out.println("重新执行任务后corePoolSize:" + executorService.getPoolSize()); } latch.await(); }}
定义了核心线程数为1,最大线程数为2,队列长度为2的线程池,程序运行时一次性给线程池安排4个任务,重复两次,下面是运行的结果截图:
从现象看,我们的问题复现出来了!已经复现那就好办了,可以打断点进去一看究竟了(为了便于分析理解,我就没有把debug截图出来,都是关键源码截图)!
我们再回顾下线程池的类图,要不然我们还不知道在哪里看,在哪里下手打断点是不是?
ThreadPoolExecutor至关重要,它就是我们所说的线程池了。Executors给了很多生成默认线程池的方法,但一般不建议用Executors的默认线程池,具体原因不在本文讨论范围之内,有兴趣可以先自行了解。
已经找到ThreadPoolExecutor关键类, 再上一个线程池运行任务的基本流程图
有了这图是不是就很清晰了?重点在「线程执行任务」里面,接下来我们开始看源码。
从ThreadPoolExecutor的execute方法看起,为什么从这里看起?因为我们的任务放到线程池后,是从调用execute执行开始的。
execute主体代码量很少,我特地圈出了addWorker方法,这个方法里面最最重要的就是初始化Workery以及启动thread了。
上图是Worker作为内部类最关键的代码,非常有意思的是Worker本身也是一个Runnable,它把自己放到自己的成员变量thread里面来执行了!thread顾名思义就是线程了。
这样就明了了,Worker实现了Runnable接口,我们直接看它的run方法,看截图的③处标记,抽离出来一个runWorker方法,我们直接看runWorker方法,看下图:
①处是一个while循环,getTask方法就是从线程池队列取任务,里面就是上面我说到的调用BlockingQueue的poll方法,超时时间即是我们配置线程池时的keepAliveTime。
②处就是我们本问题真正答案之处了呀!发现没,一旦跳出while循环,即进入到processWorkExit方法,这就是回收Worker,答案终于浮出水面,看下面截图:
细心的同学可能发现了,这里remove回收岂不是把核心线程也一起回收了?
非也,这个问题的答案在getTask方法里面,来,我们再看一下getTask方法:
①处表示是否允许核心线程超时,或者线程数是否大于核心线程数。(这里说一下一个非常细的点:线程池中如果线程数低于核心线程数,就一定不会回收线程了吗?答案显然不是,allowCoreThreadTimeOut参数不就可以实现回收了么!)
②处就是从任务队列取任务了,带了timeOut参数的poll方法超时未能从任务队列获取任务即返回null,从而实现最终的线程回收。
4
结束
我是tin,一个在努力让自己早日变成大神的普通攻城狮。阅历有限、学识浅薄,各位朋友如有发现文章不妥之处,非常欢迎加我提出,我一定细心推敲加以修改。
看到这里请给个鼓励再走吧,坚持原创不容易,不要白嫖,你的正反馈是我坚持输出的最强大动力,谢谢!
总结、提升
做一个快乐的攻城狮
构筑属于自己的一方天地
标签: #java线程池线程回收 #线程池如何回收线程 #线程资源回收 #线程回收机制