Java线程池如何保证线程安全?

在Java并发编程中,ThreadPoolExecutor类是线程池的核心,Java线程池的线程安全设计展示了并发编程的终极艺术:在保证正确性的前提下追求最大性能。每个看似简单的API背后,都凝结着无数次的JVM调优经验与并发难题的解决方案。我们来看看线程池是怎么保证并发安全的。

一、线程安全的核心设计

  1. 状态与数量的原子控制
java 复制代码
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    • 使用AtomicInteger类型的ctl变量
    • 高3位存储线程池状态(RUNNING/SHUTDOWN/STOP等)
    • 低29位存储工作线程数量
  1. 全局锁机制
java 复制代码
private final ReentrantLock mainLock = new ReentrantLock();
    • 通过mainLock(ReentrantLock实例)控制关键操作
  1. 线程安全的任务队列
    • 使用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

三、性能与安全的平衡艺术

  1. 锁粒度优化
    • 细粒度锁:Worker使用独立锁
    • 全局锁仅用于工作线程集合修改
  1. 无锁化设计
kotlin 复制代码
private boolean compareAndIncrementWorkerCount(int expect) {
    return ctl.compareAndSet(expect, expect + 1);
}
    • CAS操作替代传统锁
    • 减少线程阻塞时间
  1. 内存可见性保障
    • volatile修饰关键字段
    • happens-before原则保证状态可见性

四、从源码中收获什么?

读源码总要领悟一些设计智慧,那么通过ThreadPoolExecutor的源码,我们可以领悟到大师们的那些设计智慧?

  1. 状态优先原则
less 复制代码
if (isRunning(c) && workQueue.offer(command)) {
    // ...
}
    • 状态判断始终优先于数量判断
  1. 失败回退机制
bash 复制代码
if (!workQueue.offer(command)) {
    if (!addWorker(command, false))
        reject(command);
}
  1. 优雅降级策略
    • 当系统过载时,通过拒绝策略保证核心业务可用

"并发编程的第一要务是保证正确性,其次才是性能优化。" ------ Brian Goetz(《Java并发编程实战》作者)

相关推荐
止语Lab1 小时前
好的 DX 不等于少写代码——三种语言的摩擦力设计课
后端
吃饱了得干活1 小时前
别再手动解析 LLM 输出了!LangChain 四种结构化输出方案对比
后端·python·langchain
程序员天天困1 小时前
Arthas trace 命令怎么用?一行定位最慢那行代码
jvm·后端
Huiturn1 小时前
GPT 5.6 连续编码 10 小时,纯 Python 啃下 Word 二进制格式——doc2docx 实现拆解
后端
用户298698530141 小时前
Python 实现 Excel 与 Markdown 互转的实用指南
后端·python·excel
用户77283104908401 小时前
krono-job:零侵入、单二进制交付的分布式任务调度平台(开源)
后端
Conan在掘金1 小时前
鸿蒙报错速查:Function lacks ending return statement,返回路径漏 return 就炸,根因 + 真解法
后端
人间凡尔赛2 小时前
AI-Native 云原生架构:2026 年从容器编排到智能体编排的范式革命
后端·云原生·架构
IT_陈寒2 小时前
Vue的响应式让我加班到凌晨3点,原来问题出在这
前端·人工智能·后端
卷无止境2 小时前
提升 Python 代码健壮性的方法大盘点
后端·python