摘要:有细心的网友早就想到了这个问题在线程池中,还有一些不常用的设置。所以该方法会在线程池总预先创建没有任务执行的线程,数量为。下面我们测试一下从测试结果来看,线程池中已经预先创建了数量的空闲线程。
微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。
老司机倾囊相授,带你一路进阶,来不及解释了快上车!
看完我上一篇文章「你都理解创建线程池的参数吗?」之后,当遇到这种问题,你觉得你完全能够唬住面试官了,50k轻松到手。殊不知,要是面试官此刻给你来个反杀:
初始化线程池时可以预先创建线程吗?线程池的核心线程可以被回收吗?为什么?
如果此刻你一脸懵逼,这个要慌,问题很大,50k马上变5k。
有细心的网友早就想到了这个问题:
在ThreadPoolExecutor线程池中,还有一些不常用的设置。我建议如果您在应用场景中没有特殊的要求,就不需要使用这些设置。
初始化线程池时可以预先创建线程吗? prestartAllCoreThreads初始化线程池时是可以预先创建线程的,初始化线程池后,再调用prestartAllCoreThreads()方法,即可预先创建corePoolSize数量的核心线程,我们看源码:
public int prestartAllCoreThreads() { int n = 0; while (addWorker(null, true)) ++n; return n; }
private boolean addWorker(Runnable firstTask, boolean core) { // .. }
addWorker方法目的是在线程池中添加任务并执行,如果task为空,线程获取任务执行时调用getTask()方法,该方法从blockingQueue阻塞队列中阻塞获取任务执行,因此线程不会释放,留存在线程池中,如果core=true,说明任务只能利用核心线程来执行。
所以该方法会在线程池总预先创建没有任务执行的线程,数量为corePoolSize。
下面我们测试一下:
从测试结果来看,线程池中已经预先创建了corePoolSize数量的空闲线程。
prestartCoreThreadprestartCoreThread()同样可以预先创建线程,只不过该方法只会与创建1条线程,我们来看源码:
public boolean prestartCoreThread() { return workerCountOf(ctl.get()) < corePoolSize && addWorker(null, true); }
从方法源码可知,如果此时工作线程数量小于corePoolSize,那么就调用addWorker创建1条空闲核心线程。
下面我们测试一下:
从测试结果来看,线程池中已经预先创建了1条空闲线程。
线程池的核心线程可以被回收吗?你可能会想到将corePoolSize的数量设置为0,从而线程池的所有线程都是“临时”的,只有keepAliveTime存活时间,你的思路也许时正确的,但你有没有想过一个很严重的后果,corePoolSize=0时,任务需要填满阻塞队列才会创建线程来执行任务,阻塞队列有设置长度还好,如果队列长度无限大呢,你就等着OOM异常吧,所以用这种设置行为并不是我们所需要的。
有没有什么设置可以回收核心线程呢?
allowCoreThreadTimeOutThreadPoolExecutor有一个私有成员变量:
private volatile boolean allowCoreThreadTimeOut;
如果allowCoreThreadTimeOut=true,核心线程在规定时间内会被回收。
上面我也说了,当线程空闲时会从blockingQueue阻塞队列中阻塞获取任务执行,所以我们来看看是保证核心线程不被销毁的,我们直接定位到源码部位:
java.util.concurrent.ThreadPoolExecutor#getTask:
boolean timedOut = false; // Did the last poll() time out? for (;;) { // Are workers subject to culling? boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } }
这里的关键值timed,如果allowCoreThreadTimeOut=true或者此时工作线程大于corePoolSize,timed=true,如果timed=true,会调用poll()方法从阻塞队列中获取任务,否则调用take()方法获取任务。
下面我来解释这两个方法:
poll(long timeout, TimeUnit unit):从BlockingQueue取出一个任务,如果不能立即取出,则可以等待timeout参数的时间,如果超过这个时间还不能取出任务,则返回null;
take():从blocking阻塞队列取出一个任务,如果BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的任务被加入为止。
到这里,我们就很好地解释了,当allowCoreThreadTimeOut=true或者此时工作线程大于corePoolSize时,线程调用BlockingQueue的poll方法获取任务,若超过keepAliveTime时间,则返回null,timedOut=true,则getTask会返回null,线程中的runWorker方法会退出while循环,线程接下来会被回收。
下面我们测试一下:
可以看到,核心线程被回收了。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/77615.html
摘要:所以说我们的线程最好是交由线程池来管理,这样可以减少对线程生命周期的管理,一定程度上提高性能。线程池不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。当所有的任务已终止,记录的任务数量为,线程池会变为状态。线程池彻底终止的状态。 前言 只有光头才能变强 回顾前面: ThreadLocal就是这么简单 多线程三分钟就可以入个门了! 多线程基础必要知识点!看了学习多线程事半功倍...
摘要:此时线程需要锁才能继续往下执行。但是线程的锁并没有释放,线程的锁也没有释放。 前言 只有光头才能变强 回顾前面: ThreadLocal就是这么简单 多线程三分钟就可以入个门了! 多线程基础必要知识点!看了学习多线程事半功倍 Java锁机制了解一下 AQS简简单单过一遍 Lock锁子类了解一下 线程池你真不来了解一下吗? 本篇主要是讲解死锁,这是我在多线程的最后一篇了。主要将多线程...
摘要:的方法,的默认实现会判断是否是类型注意自动拆箱,自动装箱问题。适应自旋锁锁竞争是下的,会经过用户态到内核态的切换,是比较花时间的。在中引入了自适应的自旋锁,说明自旋的时间不固定,要不要自旋变得越来越聪明。 前言 只有光头才能变强 之前在刷博客的时候,发现一些写得比较好的博客都会默默收藏起来。最近在查阅补漏,有的知识点比较重要的,但是在之前的博客中还没有写到,于是趁着闲整理一下。 文本的...
摘要:前言本文是一篇简短的杂糅本文源自于作者最近的一个疑问为什么在旧版的中偏向锁的移除一定要在全局安全点进行同时在上个星期作者参与的一个项目发生了一件怪事一个服务莫名其妙地不接受任何请求了一切请求都是而查看日志发现出故障的服务本身去请求另一个服务 前言 本文是一篇简短的杂糅. 本文源自于作者最近的一个疑问:为什么在旧版的jdk中偏向锁的移除一定要在全局安全点进行?同时在上个星期,作者参与的一...
摘要:受知乎文章和设计模式之禅的启发,我也来搞一篇脑洞小开的文章由标题可知,这篇文章是写给我女朋友看的。于是这就让经纪人对粉丝说只有万,我才会写代码。 前言 只有光头才能变强 回顾前面: ThreadLocal就是这么简单 多线程三分钟就可以入个门了! 多线程基础必要知识点!看了学习多线程事半功倍 Java锁机制了解一下 AQS简简单单过一遍 Lock锁子类了解一下 线程池你真不来了解一下...
阅读 1789·2021-09-14 18:03
阅读 2242·2019-08-30 15:48
阅读 1095·2019-08-30 14:09
阅读 463·2019-08-30 12:55
阅读 2696·2019-08-29 11:29
阅读 1457·2019-08-26 13:43
阅读 2278·2019-08-26 13:30
阅读 2335·2019-08-26 12:17