public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
//线程池状态 高3位表示线程状态 低29位代表线程数量
int c = ctl.get();
//判断当前线程池线程数量是否小于核心线程数
if (workerCountOf(c) < corePoolSize) {
//作为核心线程数进行线程的创建,并且创建成功线程会将command的任务执行--》对应图上的直接执行
if (addWorker(command, true))
return;
c = ctl.get();
}
//创建核心线程失败或者当前线程数量超过核心线程数
//当前线程池是否还在运行状态,尝试将任务添加到阻塞队列 --》对应图上的缓冲执行
//BlockingQueue队列的顶级抽象定义了offer不是进行阻塞添加而是立即返回,添加失败直接返回false,区别于put
if (isRunning(c) && workQueue.offer(command)) {
//重新获取线程池标志位
int recheck = ctl.get();
//如果线程此时不在运行状态中,那么将任务删除
if (! isRunning(recheck) && remove(command))
//删除任务成功,走拒绝策略拒绝掉当前任务
reject(command);
else if (workerCountOf(recheck) == 0)
//如果线程池中的工作线程都没有的时候,这里需要创建一个线程去执行添加到队列中的任务
//防止因为并发的原因工作线程都被终止掉了,此时任务在阻塞队列里等着,缺没有工作线程
addWorker(null, false);
}
//到这里那就是添加队列失败,或者线程池状态异常,但是这里仍然尝试进行创建一个worker
//如果创建失败,也是走拒绝策略拒绝当前任务
else if (!addWorker(command, false))
reject(command);
}
接下来我们仔细看看 addWorker 这个方法具体是在做什么:
//核心逻辑其实就是在无限循环创建一个worker,创建失败直接返回,创建成功,则将worker执行
// 因为worker有thread的成员变量,最终添加worker成功,会启动线程的start方法
//start方法最终会回调到外层的runWorker方法,改方法会不停的从阻塞队列里以阻塞的take方式
//获取任务,除非达到能被终止的条件,此时当前线程会终止
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//不停的重试添加worker的计数,只有添加成功的才会进行后续的worker启动
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//重试期间,如果其他线程导致线程池状态不一致了。重新回到第一个循环进行check判断
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
//这里加锁一个是workers.add时需要加锁,另外是防止其他线程已经在尝试修改线程池状态了
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
//将worker的引用添加到workers的hashSet中
workers.add(w);
int s = workers.size();
//更新线程池此时最大的线程数
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//如果添加成功,就启动worker中的线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//这里添加失败的话,需要把线程池的count数进行--,并且要把worker引用从hashSer中移除
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
3.4 线程池的生命周期
在介绍运行机制原理的源码分析时,其实是有提到线程池状态这个概念的。介绍这个状态其实也是让大家更方便的去管理线程池,比如我们关闭线程池时,怎么去优雅的关闭,使用不同的方法可能会有不同的效果,我们需要根据自己的业务场景去酌情分析、权衡使用。
//线程池的状态和计数采用一个Integer变量设置的
//这里之所以用一个变量来储存状态和数量,其实很有讲究的,因为我们在上面的运行原理上可以看到
//源码中有大量的进行状态以及数量的判断,如果分开采用变量的记录的话,在维护二者一致性方面
//可能就需要加锁的维护成本了,而且计算中都是位移运算也是非常高效的
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//线程池的大小由ctl低29位表示,现成状态由ctl高3位表示
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY = (1 << COUNT_BITS) - 1;
// 线程池的状态通过简单的位移就能计算出来,状态只能从低到高流转,不能逆向
private static final int RUNNING = -1 << COUNT_BITS;
private static final int SHUTDOWN = 0 << COUNT_BITS;
private static final int STOP = 1 << COUNT_BITS;
private static final int TIDYING = 2 << COUNT_BITS;
private static final int TERMINATED = 3 << COUNT_BITS;
// 这里是获取线程状态以及获取线程数量的简单高效的位移方法
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; }
接下来结合源码详细介绍下线程池的 5 种状态以及分别有什么不同的表现行为?
先说下结论:
RUNNING 这个就是线程池运行中状态,我们可以添加任务也可以处理阻塞队列任务
SHUTDOWN 不能添加新的任务,但是会将阻塞队列中任务执行完毕
STOP 不能添加新的任务,执行中的线程也会被打断,也不会处理阻塞队列的任务
TIDYING 所有线程都被终止,并且workCount=0时会被置为的状态
TERMINATED 调用完钩子方法terminated()被置为的状态
shutdown 状态源码分析:
//线程池关闭
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
//循环cas设置线程池状态,直到成功或状态已经state>=SHUTDOWN
advanceRunState(SHUTDOWN);
//这个是真正得出结论的地方
interruptIdleWorkers();
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
tryTerminate();
}
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
//打断空闲的线程,如何判断线程是否空闲还是运行?
private void interruptIdleWorkers(boolean onlyOne) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers) {
Thread t = w.thread;
//worker的线程没有被打断过,并且能获取到worker的aqs独占锁
if (!t.isInterrupted() && w.tryLock()) {
try {
//打断当前线程,如果线程在阻塞队列中阻塞,此时会被中断
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
if (onlyOne)
break;
}
} finally {
mainLock.unlock();
}
}
STOP 状态分析
//循环cas修改线程池状态为stop。打断所有线程,取出阻塞队列的所有任务
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
//检查线程的权限
checkShutdownAccess();
//将状态case为stop
advanceRunState(STOP);
//打断所有worker不管是不是正在执行任务
interruptWorkers();
tasks = drainQueue();
} finally {
mainLock.unlock();
}
tryTerminate();
return tasks;
}
//这里获取锁之后。打断了所有的线程
private void interruptWorkers() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for (Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}
TIDYING、TERMINATED 状态分析
//这个方法在每个线程退出时都会进行调用,如果是运行中、或者状态大于等于TIDYING或者shutdown但是队列不为空都
//直接返回,如果不满足以上条件,并且线程数不为0的话,打断一个空闲线程
final void tryTerminate() {
for (;;) {
int c = ctl.get();
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
return;
if (workerCountOf(c) != 0) { // Eligible to terminate
interruptIdleWorkers(ONLY_ONE);
return;
}
//此时到这里,状态要么为STOP。要么是shutdown并且队列为空了
// 获取一个锁,尝试cas修改状态为TIDYING
//调用terminated()的钩子方法,
//修改线程池为终态TERMINATED,并且唤醒阻塞在termination队列上的线程
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
terminated();
} finally {
ctl.set(ctlOf(TERMINATED, 0));
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// else retry on failed CAS
}
}
四、JDK 内置线程池的问题
java.util.concurrent.Executors 工厂类提供了一些静态方法,方便我们快速创建几种预设配置的线程池:
- Executors.newFixedThreadPool(int nThreads):
- 创建一个固定大小的线程池。corePoolSize 和 maximumPoolSize 都等于 nThreads。
- keepAliveTime 为 0L(因为线程数不会超过 corePoolSize,所以此参数无效,除非 allowCoreThreadTimeOut 为 true)。
- 使用无界的 LinkedBlockingQueue 作为工作队列。
- 问题:由于使用无界队列,当任务提交速度远大于处理速度时,队列会持续增长,可能导致内存溢出(OOM)。此时 maximumPoolSize 参数实际上是无效的,线程数永远不会超过 nThreads。
- Executors.newSingleThreadExecutor():
- 创建一个只有一个工作线程的线程池。corePoolSize 和 maximumPoolSize 都为 1。
- 同样使用无界的 LinkedBlockingQueue。
- 保证所有任务按照提交顺序(FIFO)执行。
- 问题:与 newFixedThreadPool 类似,无界队列可能导致 OOM。
- Executors.newCachedThreadPool():
- 创建一个可缓存的线程池。
- corePoolSize 为 0。
- maximumPoolSize 为 Integer.MAX_VALUE (几乎是无界的)。
- keepAliveTime 为 60 秒。
- 使用 SynchronousQueue 作为工作队列。这种队列不存储元素,任务提交后必须有空闲线程立即接收,否则会创建新线程(如果未达到 maximumPoolSize)。
- 问题:如果任务提交速度过快,会创建大量线程(理论上可达 Integer.MAX_VALUE 个),可能耗尽系统资源,导致 OOM 以及频繁的上下文切换。
790

被折叠的 条评论
为什么被折叠?



