线程池解决的核心问题:资源管理问题。
线程池运行机制最主要的三个点:
线程池如何维护自身状态;
线程池如何管理任务;
线程池如何管理线程。\
实际上,上面的说法不够准确,线程池并没有对线程做标记,并表示某个线程不能被销毁。线程在初始创建的时候,会进行一系列的判断,如果小于核心线程数,那么addWorker(firstTask, true),创建时会将当前的runnable在调用start()方法时直接执行,后续会一直getTask();如果大于核心线程数,那么放进缓冲队列中;缓冲队列满了,会addWorker(null, false),会从缓冲队列中getTask()。如果getTask()返回的为null,就会销毁该线程。
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?
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
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;
}
}
}
这里的关键值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循环,线程接下来会被回收。
生命周期:
线程池运行的状态,并不是用户显式设置的,而是伴随着线程池的运行,由内部来维护。线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起,如下代码所示:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
ctl这个AtomicInteger类型,是对线程池的运行状态和线程池中有效线程的数量进行控制的一个字段, 它同时包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。通过阅读线程池源代码也可以发现,经常出现要同时判断线程池运行状态和线程数量的情况。线程池也提供了若干方法去供用户获得线程池当前的运行状态、线程个数。这里都使用的是位运算的方式,相比于基本运算,速度也会快很多。
关于内部封装的获取生命周期状态、获取线程池线程数量的计算方法如以下代码所示:
private static int runStateOf(int c) { return c & ~CAPACITY; } //计算当前运行状态 private static int workerCountOf(int c) { return c & CAPACITY; } //计算当前线程数量 private static int ctlOf(int rs, int wc) { return rs | wc; } //通过状态和线程数生成ctl
ThreadPoolExecutor的运行状态有5种,分别为:
need-to-insert-img
其生命周期转换如下入所示:
need-to-insert-img
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demp-pool-%d").build();
ThreadPoolExecutor executor = new ThreadPoolExecutor(1, 1, 1L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(5),
namedThreadFactory,
new ThreadPoolExecutor.CallerRunsPolicy()
);
executor.allowCoreThreadTimeOut(true);
AtomicInteger atomicInteger = new AtomicInteger();
for(int i = 0; i <= 4; i++){
executor.execute(() -> {
LOG.debug(String.valueOf(atomicInteger.wait(10000)));
});
}
System.out.println("");
参考:https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
https://objcoding.com/2019/04/14/threadpool-some-settings/