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并发编程实战》作者)

相关推荐
来自星星的坤2 小时前
SpringBoot 与 Vue3 实现前后端互联全解析
后端·ajax·前端框架·vue·springboot
AUGENSTERN_dc2 小时前
RaabitMQ 快速入门
java·后端·rabbitmq
烛阴2 小时前
零基础必看!Express 项目 .env 配置,开发、测试、生产环境轻松搞定!
javascript·后端·express
燃星cro2 小时前
参照Spring Boot后端框架实现序列化工具类
java·spring boot·后端
追逐时光者5 小时前
C#/.NET/.NET Core拾遗补漏合集(25年4月更新)
后端·.net
FG.5 小时前
GO语言入门
开发语言·后端·golang
转转技术团队6 小时前
加Log就卡?不加Log就瞎?”——这个插件治好了我的精神
java·后端
谦行6 小时前
前端视角 Java Web 入门手册 5.5:真实世界 Web 开发——控制反转与 @Autowired
java·后端
uhakadotcom7 小时前
PyTorch 2.0:最全入门指南,轻松理解新特性和实用案例
后端·面试·github
bnnnnnnnn7 小时前
前端实现多服务器文件 自动同步宝塔定时任务 + 同步工具 + 企业微信告警(实战详解)
前端·javascript·后端