一文搞懂Java并发工具(LockSupport/AQS/锁/同步器/阻塞队列/线程池)
前言
Java并发编程是后端面试核心重难点,java.util.concurrent包提供了一整套成熟并发工具,从底层线程阻塞工具LockSupport、同步器AQS,到各类锁、同步等待工具、阻塞队列、线程池,层层依赖、环环相扣。
本文按照底层→中层→上层完整链路梳理,吃透原理、对比差异、附实战代码,面试、开发一套搞定。
一、底层基石:LockSupport 线程阻塞唤醒工具
1. 核心定位
所有锁、AQS底层依赖的线程阻塞工具,专门负责线程park阻塞、unpark唤醒,替代传统wait/notify,解决原生监视器锁诸多痛点。
核心机制:许可模型,每个线程独有许可槽,只有0/1两种状态,不可累加。
2. 许可执行逻辑
park():无许可则阻塞;有许可则消耗许可(置0)直接放行;unpark(Thread t):给指定线程发放许可,槽为0则改为1,已有1无任何操作;- 优势:支持先唤醒后阻塞,不会丢失唤醒信号。
3. LockSupport vs Object.wait/notify
| 对比维度 | LockSupport | wait/notify |
|---|---|---|
| 调用前提 | 无需持有任何锁 | 必须包裹synchronized |
| 唤醒精度 | 精准指定目标线程 | 随机唤醒/全部唤醒 |
| 执行顺序 | 先unpark后park也生效 | 先notify后wait永久阻塞 |
| 解除阻塞 | unpark、中断、超时 | notify、中断、超时 |
4. 代码示例
public class LockSupportDemo {
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(() -> {
System.out.println("线程A阻塞等待许可");
LockSupport.park();
System.out.println("线程A拿到许可继续执行");
}, "A");
threadA.start();
Thread.sleep(1000);
new Thread(() -> {
System.out.println("线程B给A发放许可");
LockSupport.unpark(threadA);
}).start();
}
}
5. 中断特性
park()被线程中断时不会抛出异常,仅直接返回,需手动通过Thread.interrupted()判断中断标记。
二、同步器骨架:AQS 抽象队列同步器
1. 核心思想:模板方法模式
AQS封装线程排队、阻塞唤醒通用逻辑,子类仅需实现少量钩子方法定义「加锁/释放锁」规则,不用重复处理线程阻塞逻辑。
所有JUC锁、同步工具底层均基于AQS:ReentrantLock、读写锁、CountDownLatch、Semaphore。
2. 两大核心数据结构
(1)volatile int state 同步状态
state含义由子类自定义,通过CAS原子修改保证线程安全:
- ReentrantLock:state=重入次数,0无锁;
- ReentrantReadWriteLock:高16位读计数、低16位写计数;
- CountDownLatch:倒计时计数器;
- Semaphore:可用许可数量。
(2)CLH双向同步等待队列
存储抢锁失败的线程,节点Node核心状态waitStatus:
SIGNAL(-1):后继线程需要我唤醒;CANCELLED(1):线程取消等待;CONDITION(-2):当前节点在Condition条件队列;PROPAGATE(-3):共享锁唤醒传播;0:初始状态。
3. 两种工作模式
独占模式(ReentrantLock)
流程:acquire()
tryAcquire()尝试抢锁,成功直接返回;- 失败创建EXCLUSIVE节点入CLH队列;
acquireQueued循环:前驱是头节点则重试抢锁,否则LockSupport.park()阻塞;- 被唤醒后重复抢锁逻辑。
钩子方法:tryAcquire/tryRelease/isHeldExclusively
共享模式(CountDownLatch/Semaphore)
流程:releaseShared()
tryReleaseShared释放资源,state变更成功;doReleaseShared唤醒后继节点,共享锁会链式传播唤醒所有等待线程。
钩子方法:tryAcquireShared/tryReleaseShared
4. 自定义同步器开发步骤
- 继承
AbstractQueuedSynchronizer; - 根据独占/共享重写对应钩子;
- 对外封装lock/unlock等业务API。
三、独占锁:ReentrantLock 可重入锁
1. 可重入原理
AQS中exclusiveOwnerThread记录持有锁线程,同一线程重复加锁时state自增,释放时递减至0才完全释放,支持多层嵌套。
2. 公平锁 vs 非公平锁
- 非公平锁(默认,性能更高)
抢锁前不检查等待队列,直接CAS抢占,减少线程切换开销; - 公平锁
加锁前校验CLH队列是否有前驱等待线程,有则排队,严格FIFO。
3. Condition 多条件等待机制
一把锁可创建多个独立Condition,解决synchronized单等待队列只能随机唤醒的缺陷。
底层结构
每个Condition维护单向条件队列(节点waitStatus=-2),与CLH同步队列隔离。
执行流程
await():释放全部锁,线程加入当前Condition条件队列阻塞;signal():取出条件队列头节点,迁移至CLH同步队列等待抢锁;signalAll():迁移条件队列全部节点到同步队列。
Condition vs wait/notify
| 维度 | Condition | wait/notify |
|---|---|---|
| 等待队列 | 多条件队列,精准唤醒 | 仅有1个隐式队列 |
| 唤醒粒度 | 仅唤醒当前条件线程 | 随机/全部唤醒 |
| 依赖 | ReentrantLock | synchronized监视器 |
4. ReentrantLock 与 synchronized 对比
| 对比 | ReentrantLock | synchronized |
|---|---|---|
| 实现 | Java层AQS | JVM底层C++ |
| 重入计数 | 可通过API获取 | 无对外接口 |
| 释放 | 必须手动unlock | 代码块自动释放 |
| 拓展 | 公平锁、多Condition、可中断 | 功能单一 |
四、读写锁:ReentrantReadWriteLock
1. 设计目标
读多写少场景提升并发:读共享、写独占,多线程可同时读,写操作阻塞所有读写。
2. state位拆分
32位int state高低16位拆分:
- 低16位:写锁重入计数;
- 高16位:全局读锁总计数;
static final int SHARED_SHIFT = 16;
static final int EXCLUSIVE_MASK = (1 << 16) - 1;
// 获取写计数:state & EXCLUSIVE_MASK
// 获取读计数:state >>> SHARED_SHIFT
3. 读写锁重入实现
- 写锁:同ReentrantLock,state低16位计数;
- 读锁:
ThreadLocal<HoldCounter>记录单线程读重入次数,高16位全局累加。
4. 锁降级(允许)、锁升级(禁止)
- 锁降级(写→读)
持有写锁时获取读锁,再释放写锁;保证自己修改的数据对后续读线程可见,无死锁。 - 锁升级(读→写)
两个线程同时持有读锁,都等待对方释放读锁才能获取写锁,互相阻塞,永久死锁,JDK禁止。
五、三大同步等待工具(CountDownLatch / CyclicBarrier / Semaphore)
1. CountDownLatch 倒计时门栓
核心能力
主线程等待N个子线程全部完成,一次性不可重置,共享AQS实现。
- 初始化state=N;
countDown():CAS递减state;await():state>0则阻塞,归零后全部唤醒。
典型场景
- 服务启动等待缓存、DB、配置初始化;
- 并发压测统一发令、批量任务结果汇总。
双Latch经典并发示例
CountDownLatch start = new CountDownLatch(1);
CountDownLatch done = new CountDownLatch(N);
// N个线程全部await(start)统一并发;主线程await(done)等待全部结束
2. CyclicBarrier 循环屏障
核心能力
N个线程互相等待,全部到达屏障点后统一放行,可循环复用,支持屏障后置任务。
典型场景
多阶段批量数据处理,每阶段所有线程就绪再执行下一阶段。
3. Semaphore 信号量限流工具
核心能力
控制同时访问资源的最大线程数,底层AQS共享模式,许可可动态增减。
acquire():获取许可,不足则阻塞;release():归还许可,唤醒等待线程。
典型场景
接口限流、数据库连接池并发控制。
三者核心区别总结
- CountDownLatch:一方等多方,一次性;
- CyclicBarrier:多方互相等待,可循环;
- Semaphore:控制并发资源数量,限流专用。
六、阻塞队列 BlockingQueue
1. 核心特性
线程安全,自带阻塞读写:
put(e):队列满阻塞写入线程;take():队列为空阻塞读取线程。
2. ArrayBlockingQueue(有界数组队列)
- 固定容量,单把ReentrantLock,读写互斥;
- 两个Condition:notEmpty、notFull;
- 连续数组内存,并发读写竞争激烈时性能较差。
3. LinkedBlockingQueue(链表队列)
- 默认无界,可指定容量;
- 双锁设计:takeLock、putLock,读写并行,高并发性能更优;
- 动态创建节点,存在内存碎片。
4. 底层阻塞逻辑(ArrayBlockingQueue举例)
// put流程
lock.lock();
try {
while(队列满) notFull.await();
入队;
notEmpty.signal();
} finally { lock.unlock(); }
// take流程
lock.lock();
try {
while(队列为空) notEmpty.await();
出队;
notFull.signal();
} finally { lock.unlock(); }
七、线程池 ThreadPoolExecutor
1. 创建线程池的意义
频繁创建销毁线程存在内核切换开销,线程池复用线程、管控并发,防止线程爆炸耗尽系统资源。
2. 七大核心构造参数
public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数(核心+非核心)
long keepAliveTime, // 非核心空闲超时
TimeUnit unit,
BlockingQueue<Runnable> workQueue, // 等待队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
3. 任务执行完整流程
- 线程数 < corePoolSize:新建核心线程执行;
- 核心线程已满:任务存入阻塞队列;
- 队列已满:创建非核心线程;
- 线程达到maximumPoolSize:执行拒绝策略。
4. 四种拒绝策略
AbortPolicy默认:直接抛出异常;CallerRunsPolicy:提交线程自行执行,天然限流回压,生产推荐;DiscardPolicy:静默丢弃任务;DiscardOldestPolicy:丢弃队列最旧任务,放入新任务。
5. CompletableFuture 自定义线程池
CompletableFuture无参异步方法默认使用ForkJoinPool,高并发IO场景建议传入自定义ThreadPoolExecutor隔离线程资源,避免公共池阻塞。
八、全文总结
- 底层阻塞:LockSupport提供线程park/unpark,是所有同步组件基础;
- 同步骨架:AQS通过state+CLH队列定义统一同步模板,独占/共享两种模式;
- 锁层:ReentrantLock独占可重入、多Condition精准等待;ReentrantReadWriteLock读写分离优化读多写少;
- 同步工具:CountDownLatch(等待完成)、CyclicBarrier(互相等待)、Semaphore(限流);
- 阻塞队列:基于ReentrantLock+Condition实现线程安全阻塞读写;
- 线程池:复用线程、管控并发,配套拒绝策略防止雪崩。
整套工具自上而下层层依赖,面试常考底层联动原理、各类工具场景对比,开发中根据业务并发模型合理选择即可。

浙公网安备 33010602011771号