在Java并发编程中,
ThreadPoolExecutor
类是线程池的核心,Java线程池的线程安全设计展示了并发编程的终极艺术:在保证正确性的前提下追求最大性能。每个看似简单的API背后,都凝结着无数次的JVM调优经验与并发难题的解决方案。我们来看看线程池是怎么保证并发安全的。
一、线程安全的核心设计
- 状态与数量的原子控制
java
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
-
- 使用
AtomicInteger
类型的ctl
变量 - 高3位存储线程池状态(RUNNING/SHUTDOWN/STOP等)
- 低29位存储工作线程数量
- 使用
- 全局锁机制
java
private final ReentrantLock mainLock = new ReentrantLock();
-
- 通过
mainLock
(ReentrantLock实例)控制关键操作
- 通过
- 线程安全的任务队列
-
- 使用
BlockingQueue
实现生产-消费模型 - 常见实现:LinkedBlockingQueue、ArrayBlockingQueue
- 使用
二、线程安全的三重保障
1. 线程创建的安全性保障
typescript
public void execute(Runnable command) {
// 状态校验...
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true)) // 原子操作
return;
// ...
}
// ...
}
- 通过
addWorker()
方法保证创建工作线程的原子性 - CAS操作保证线程数量准确递增
ThreadPoolExecutor在创建线程的全部过程中,使用悲观锁锁住的部分仅仅只有再次检查线程池状态和线程入队列这两个步骤的几行代码,其他全部使用乐观锁来完成。
刻意将线程创建和线程启动两个步骤细分开来,最大限度减小悲观锁的加锁区域。而且线程池状态变更比线程数量变更的频率低很多,只在检查线程池状态时加悲观锁也最大限度减少了锁竞争的发生频率。
线程创建
csharp
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
//两个for循环,外层循环检查线程池状态是否运行创建新线程
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// 1.0 检查线程池状态是否允许创建新线程
if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))
return false;
//内层循环检查工作线程数量是否在要求范围之内
for (;;) {
// 2.0 获取工作线程数量,并且判断数量是否超标
int wc = workerCountOf(c);
if (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))
return false;
// 2.1 先比较ctl的当前实际值有没有改变没有改变的话将ctl自增1,
// 然后跳出循环,进行下一步创建线程
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
//3.0 判读线程池的状态是否发生改变,发生变化需要从头开始重新获取线程池状态,
//即重新执行外部的大循环
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
线程启动
ini
//新线程是否已经开始运行 和 是否已经加入队列
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
//1.0 构造器中利用线程工厂得到新线程对象
w = new Worker(firstTask);
//1.1 获得这个新线程
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
//2.0 加锁,在新线程加入workers队列时不允许有其他线程改变线程池状态
mainLock.lock();
try {
//2.1 再次检查线程池状态,是否运行创建新的worker
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//3.0 新线程肯定是还没有开始运行的,这里可能是程序员自定义ThreadFactory
//实现类时在内部就start了
if (t.isAlive())
throw new IllegalThreadStateException();
//3.1 worker加入线程队列
workers.add(w);
//3.2 调整largestPoolSize值
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
//4.0 加入集合成功,使用线程的start()方法启动线程
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
//4.1 检测线程池状态发生变化后,那新线程不能被启动,则需要把已创建的Worker从集合中移除,
// 并且把ctl的线程数量部分再减1,其实就是addWorkerFailed进行回滚。
if (! workerStarted)
addWorkerFailed(w);
}
2. 工作线程的生命周期管理
每个Worker继承AQS实现独占锁, 防止任务执行期间被中断导致状态不一致。并实现了如下的方法。
typescript
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
我们来看看某些集合核心方法是怎么保证线程安全的。
ini
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
try {
while (task != null || (task = getTask()) != null) {
w.lock(); // 获取Worker锁
try {
beforeExecute(wt, task);
try {
task.run();
} finally {
afterExecute(task, null);
}
} finally {
task = null;
w.completedTasks++;
w.unlock(); // 释放锁
}
}
} finally {
processWorkerExit(w, completedAbruptly);
}
}
3. 状态转换的屏障
csharp
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) ||
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
break;
}
}
- 使用CAS保证状态转换的原子性
- 状态转换顺序:RUNNING -> SHUTDOWN -> STOP -> TIDYING -> TERMINATED
三、性能与安全的平衡艺术
- 锁粒度优化
-
- 细粒度锁:Worker使用独立锁
- 全局锁仅用于工作线程集合修改
- 无锁化设计
kotlin
private boolean compareAndIncrementWorkerCount(int expect) {
return ctl.compareAndSet(expect, expect + 1);
}
-
- CAS操作替代传统锁
- 减少线程阻塞时间
- 内存可见性保障
-
- volatile修饰关键字段
- happens-before原则保证状态可见性
四、从源码中收获什么?
读源码总要领悟一些设计智慧,那么通过ThreadPoolExecutor
的源码,我们可以领悟到大师们的那些设计智慧?
- 状态优先原则
less
if (isRunning(c) && workQueue.offer(command)) {
// ...
}
-
- 状态判断始终优先于数量判断
- 失败回退机制
bash
if (!workQueue.offer(command)) {
if (!addWorker(command, false))
reject(command);
}
- 优雅降级策略
-
- 当系统过载时,通过拒绝策略保证核心业务可用
"并发编程的第一要务是保证正确性,其次才是性能优化。" ------ Brian Goetz(《Java并发编程实战》作者)