前言
线程池基本上是每个业务都接触的,然而线程池是怎么复用线程,线程是怎么自动超时回收♻️,core核心线程为什么不回收,一直没有过多关注,最近有疑问这些事怎么实现的,偶有所得。
1. 原理分析
其实很早之前,笔者就分析了ThreadPoolExecutor的源码,只是并没有针对线程复用,回收的角度去分析原理,单纯的分析了源码主逻辑。
(65条消息) JDK8线程池-ThreadPoolExecutor源码解析_fenglllle的博客-CSDN博客https://blog.csdn.net/fenglllle/article/details/82790242?spm=1001.2014.3001.5502
关键还是runWorker,其他核心代码见上面的链接。
1.1 线程池线程复用的秘密
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
这里 while (task != null || (task = getTask()) != null)
可以看出只要能获取task,那么循环就不会结束,这就是线程池的线程复用的秘密,就是让线程在有活干的时候,拼命干活,不休息,😝
那么线程池是怎么回收的呢,其实也很简单,拿不到task就会没活干,就释放了,线程自然运行结束销毁,就是活干完了。
那么线程池如何实现延时回收多余core的线程,又是如何定时回收的呢,如何保证core线程不回收
1.2 线程池线程回收的秘密
关键还是在获取task的代码
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
//是否core线程超时回收或者工作线程超过core
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
//CAS技术
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
Runnable r = timed ?
//队列按照设置的超时,获取任务
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
//阻塞,直到拿到任务
workQueue.take();
if (r != null)
return r;
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
可以看到allowCoreThreadTimeOut默认是不超时的,当work线程<= core时,就会阻塞当前线程workQueue.take();
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
此时就是只剩下核心线程了,默认是不超时回收的,就阻塞在队列这里。
如果>core,那么就超时等待队列获取任务,如果有任务就继续干活😄,否则就标记timeOut为true,在下次循环CAS技术减掉work线程数直接返回,这就是超时回收的原理,没任务了,线程自己结束生命周期了。
验证
上面仅仅是理论分析,实际证明线程复用,超时,核心线程的现象
public class ThreadMain {
private static final AtomicInteger atomicInteger = new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
5,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("----------haha-mime---------"+atomicInteger.getAndIncrement());
return t;
}
});
int i=0;
while (i < 200) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
i++;
}
Thread.sleep(120000);
System.out.println("=================== end =================");
}
}
执行后打印,可以说明只创建了5个线程,也可以断点运行,实际启动5个线程,而且启动了5个线程,并且执行了自定义拒绝策略(我直接修改的源码,使用jdk源码需要注入自定义拒绝策略的对象)。
等待一段时间,线程dump,可以看到,线程正在等待超时时间获取任务
再等一段时间,dump线程,剩下core线程正被队列阻塞
总结
线程池本质就是很多线程的管理,线程还是遵循生命周期,只是统计了数量,加入了队列,通过队列任务管理来达到复用线程的能力,使用阻塞来操作线程的生命周期,来达到线程的超时销毁、核心线程一直运行的目的。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)