一、理论知识与核心概念
1.1 什么是AQS?
AQS(AbstractQueuedSynchronizer,抽象队列同步器)是Java并发包(JUC)中最核心的基础框架之一,由Doug Lea大师设计并实现。它为构建锁和同步器提供了一套通用的框架,通过内置的FIFO双向队列来管理线程的竞争和等待。
简单来说,AQS解决了实现同步器时的大量通用性工作,包括:
- 同步状态的原子性管理
- 线程的阻塞与唤醒
- 等待队列的维护
开发者只需要继承AQS并实现特定的几个方法,就能构建出各种功能强大的同步工具。
1.2 AQS在Java并发包中的地位
AQS是整个JUC包的基石,以下同步工具都基于AQS实现:
独占锁:
ReentrantLock- 可重入独占锁ReentrantReadWriteLock.WriteLock- 写锁
共享锁:
Semaphore- 信号量CountDownLatch- 倒计数门闩ReentrantReadWriteLock.ReadLock- 读锁
其他同步工具:
CyclicBarrier- 循环屏障(基于ReentrantLock+Condition)ThreadPoolExecutor.Worker- 线程池工作线程(内部使用AQS)
可以说,掌握AQS原理是深入理解Java并发编程的必经之路。
1.3 AQS的设计思想:模板方法模式
AQS采用了模板方法设计模式:
AQS定义了同步器的骨架流程:
java
// 模板方法 - 获取锁的标准流程
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 尝试获取(子类实现)
acquireQueued( // 进入队列等待(AQS实现)
addWaiter(Node.EXCLUSIVE), arg)) // 加入队列(AQS实现)
selfInterrupt();
}
子类只需实现具体的获取/释放逻辑:
java
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
这种设计让AQS复用了线程调度、队列管理等复杂逻辑,子类只关注业务语义。
1.4 同步器与锁的关系
锁是面向使用者的API , 而同步器是面向锁实现者的框架:
java
// 锁 - 对外API
public class ReentrantLock implements Lock {
private final Sync sync; // 内部同步器
public void lock() {
sync.acquire(1); // 委托给同步器
}
}
// 同步器 - 实现细节
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
protected final boolean tryAcquire(int acquires) {
// 具体的锁获取逻辑
}
}
关键区别:
- 锁定义了
lock()/unlock()等用户友好的API - 同步器负责实际的线程竞争、排队、唤醒等底层机制
- 一个锁可以有多种同步器实现(如公平锁/非公平锁)
二、原理深度剖析
2.1 AQS核心设计思想
2.1.1 同步状态(state)
AQS使用一个volatile int类型的成员变量state来表示同步状态:
java
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
state的语义由子类定义:
| 同步器 | state含义 | 获取条件 |
|---|---|---|
| ReentrantLock | 重入次数 | state=0表示未锁定 |
| Semaphore | 剩余许可数 | state>0表示有许可 |
| CountDownLatch | 倒计数值 | state=0表示门闩打开 |
| ReentrantReadWriteLock | 高16位:读锁数 低16位:写锁数 | 复合判断 |
CAS操作保证原子性:
java
// 非公平锁的快速获取
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// CAS原子操作抢占锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
return false;
}
2.1.2 CLH队列锁
AQS内部维护了一个基于CLH(Craig, Landin, and Hagersten)队列锁的变体:
原始CLH队列特点:
- 自旋锁,基于链表
- 每个节点自旋检查前驱节点的状态
- 前驱释放锁时,后继自动获得锁
AQS的改进:
- 使用双向链表(便于取消操作)
- 节点不再自旋,而是阻塞等待(park/unpark)
- 前驱节点负责唤醒后继节点
Node节点结构:
java
static final class Node {
// 模式
static final Node SHARED = new Node(); // 共享模式
static final Node EXCLUSIVE = null; // 独占模式
// 等待状态
static final int CANCELLED = 1; // 线程已取消
static final int SIGNAL = -1; // 后继需要唤醒
static final int CONDITION = -2; // 在条件队列中等待
static final int PROPAGATE = -3; // 共享模式下传播唤醒
volatile int waitStatus; // 当前节点状态
volatile Node prev; // 前驱节点
volatile Node next; // 后继节点
volatile Thread thread; // 等待的线程
Node nextWaiter; // 条件队列下一个节点/模式标记
}
队列结构示意(见下图):
双向链表的维护:
java
// 入队 - 通过CAS将节点加到队尾
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // 队列为空,初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) { // CAS设置tail
t.next = node;
return t;
}
}
}
}
2.1.3 独占模式与共享模式
独占模式(Exclusive):
- 同一时刻只有一个线程能持有锁
- 典型应用:ReentrantLock、WriteLock
共享模式(Shared):
- 同一时刻允许多个线程同时持有
- 典型应用:Semaphore、ReadLock、CountDownLatch
实现差异:
java
// 独占模式 - 获取成功后只唤醒一个后继
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
// 共享模式 - 获取成功后传播唤醒
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// 如果还有剩余资源,继续唤醒后继
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // 传播唤醒
}
}
2.2 AQS核心方法源码剖析
2.2.1 acquire(int arg) - 独占式获取
完整流程:

源码分析:
java
/**
* 独占模式获取,忽略中断
* 1. 尝试获取(tryAcquire)
* 2. 失败则加入队列并阻塞(acquireQueued)
* 3. 被唤醒后重新尝试获取
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) && // ①尝试获取
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // ②入队+自旋
selfInterrupt(); // ③补偿中断
}
// ① tryAcquire - 子类实现,定义获取语义
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
// ② addWaiter - 创建节点并加入队尾
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 快速入队尝试
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 快速失败,调用完整入队逻辑
enq(node);
return node;
}
// ③ acquireQueued - 在队列中自旋获取
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) { // 自旋
final Node p = node.predecessor();
// 前驱是head才有资格尝试获取
if (p == head && tryAcquire(arg)) {
setHead(node); // 获取成功,设为新head
p.next = null; // 帮助GC
failed = false;
return interrupted;
}
// 检查是否需要阻塞
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) // 阻塞等待
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node); // 异常情况取消获取
}
}
// 判断是否应该阻塞
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true; // 前驱状态为SIGNAL,可以安心阻塞
if (ws > 0) {
// 前驱已取消,跳过所有已取消的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 设置前驱状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 阻塞当前线程
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); // 阻塞
return Thread.interrupted(); // 返回中断状态并清除中断标志
}
关键点:
- 自旋与阻塞结合: 先自旋几次,失败后才阻塞,减少线程切换
- 只有head的后继能尝试获取: 保证FIFO公平性
- 前驱负责唤醒后继: 通过waitStatus=SIGNAL标识
- 中断处理: 获取过程中不响应中断,只记录标志,最后补偿
2.2.2 release(int arg) - 独占式释放
完整流程:

源码分析:
java
/**
* 独占模式释放
* 1. 尝试释放(tryRelease)
* 2. 成功则唤醒后继节点
*/
public final boolean release(int arg) {
if (tryRelease(arg)) { // ①尝试释放
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); // ②唤醒后继
return true;
}
return false;
}
// ① tryRelease - 子类实现
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
// ② unparkSuccessor - 唤醒后继节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 清除信号
Node s = node.next;
// 如果后继为空或已取消,从tail向前找最近的有效节点
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); // 唤醒线程
}
为什么从tail向前遍历?
考虑并发入队场景:
java
// addWaiter中的入队操作
node.prev = pred; // ①设置prev
if (compareAndSetTail(pred, node)) { // ②CAS设置tail
pred.next = node; // ③设置next
return node;
}
在②③之间,next指针尚未设置,但prev已经设置。从后向前遍历能保证找到所有已入队的节点。
2.2.3 acquireShared(int arg) - 共享式获取
源码分析:
java
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0) // 返回值:负数-失败,0-成功但无剩余,正数-成功且有剩余
doAcquireShared(arg);
}
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED); // 共享模式节点
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r); // 关键:传播唤醒
p.next = null;
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// 共享模式的核心:传播唤醒
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
// propagate > 0表示还有剩余资源,继续唤醒后继
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // 释放共享锁,唤醒后继
}
}
共享模式与独占模式的关键区别:
- 独占: 只唤醒一个后继,后继成为新head
- 共享: 唤醒后继,后继继续唤醒下一个,形成传播链
2.2.4 条件队列(Condition)
ConditionObject实现原理:
java
public class ConditionObject implements Condition {
// 条件队列头尾指针(单向链表)
private transient Node firstWaiter;
private transient Node lastWaiter;
// await - 释放锁,加入条件队列,等待signal
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter(); // ①加入条件队列
int savedState = fullyRelease(node); // ②完全释放锁
int interruptMode = 0;
// ③不在同步队列中则阻塞
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// ④被signal后,重新竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters(); // 清理取消节点
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
// signal - 将条件队列头节点转移到同步队列
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first); // 转移到同步队列
}
private void doSignal(Node first) {
do {
if ((firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && // 转移节点
(first = firstWaiter) != null);
}
}
// 将节点从条件队列转移到同步队列
final boolean transferForSignal(Node node) {
// CAS修改状态从CONDITION到0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
// 加入同步队列
Node p = enq(node);
int ws = p.waitStatus;
// 如果前驱已取消或设置SIGNAL失败,直接唤醒
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
条件队列 vs 同步队列:
| 维度 | 条件队列 | 同步队列 |
|---|---|---|
| 数据结构 | 单向链表 | 双向链表 |
| 节点状态 | CONDITION(-2) | SIGNAL(-1)等 |
| 触发条件 | await()主动进入 | 锁竞争失败进入 |
| 退出条件 | signal()唤醒 | 前驱释放锁 |
| 多条件支持 | 一个Lock多个Condition | 一个同步队列 |
Condition使用场景:
生产者-消费者模式:
java
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition(); // 非满条件
final Condition notEmpty = lock.newCondition(); // 非空条件
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 队列满,等待notFull信号
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // 通知消费者
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 队列空,等待notEmpty信号
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal(); // 通知生产者
return x;
} finally {
lock.unlock();
}
}
}
2.3 ReentrantLock vs synchronized深度对比
2.3.1 功能对比
| 功能 | synchronized | ReentrantLock |
|---|---|---|
| 可重入性 | ✅支持 | ✅支持 |
| 公平性选择 | ❌不支持(非公平) | ✅支持公平/非公平 |
| 可中断获取 | ❌不支持 | ✅lockInterruptibly() |
| 超时获取 | ❌不支持 | ✅tryLock(timeout) |
| 非阻塞获取 | ❌不支持 | ✅tryLock() |
| 条件变量 | ❌只有一个(wait/notify) | ✅多个Condition |
| 锁绑定 | ❌绑定对象 | ✅可绑定任意对象 |
| 自动释放 | ✅JVM自动 | ❌需手动unlock |
| 死锁检测 | ✅JVM支持 | ❌需自行实现 |
代码示例对比:
java
// synchronized - 简洁但功能受限
public synchronized void method() {
// 业务逻辑
}
// ReentrantLock - 灵活但需手动管理
private final ReentrantLock lock = new ReentrantLock(true); // 公平锁
public void method() {
lock.lock();
try {
// 业务逻辑
} finally {
lock.unlock(); // 必须在finally中释放
}
}
// 可中断获取
public void methodWithInterrupt() throws InterruptedException {
lock.lockInterruptibly();
try {
// 长时间操作
} finally {
lock.unlock();
}
}
// 超时获取
public boolean methodWithTimeout() {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 业务逻辑
return true;
} finally {
lock.unlock();
}
}
return false; // 获取锁超时
}
2.3.2 性能对比
JDK 1.6之前:
- synchronized是重量级锁,完全依赖操作系统互斥量
- ReentrantLock完全在用户态实现,性能明显优于synchronized
JDK 1.6之后(锁优化):
- synchronized引入偏向锁、轻量级锁、自旋锁等优化
- 低竞争场景下,synchronized性能与ReentrantLock相当甚至更好
性能测试(JDK 17):
java
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
@State(Scope.Benchmark)
public class LockBenchmark {
private final Object syncLock = new Object();
private final ReentrantLock reentrantLock = new ReentrantLock();
private int count = 0;
// 低竞争场景(1线程)
@Benchmark
@Threads(1)
public void synchronizedLowContention() {
synchronized (syncLock) {
count++;
}
}
@Benchmark
@Threads(1)
public void reentrantLockLowContention() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock();
}
}
// 高竞争场景(16线程)
@Benchmark
@Threads(16)
public void synchronizedHighContention() {
synchronized (syncLock) {
count++;
}
}
@Benchmark
@Threads(16)
public void reentrantLockHighContention() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock();
}
}
}
测试结果(ops/s,越高越好):
| 场景 | synchronized | ReentrantLock(非公平) | ReentrantLock(公平) |
|---|---|---|---|
| 低竞争(1线程) | 120M | 115M | 110M |
| 高竞争(16线程) | 8M | 12M | 6M |
结论:
- 低竞争: synchronized略优(JVM优化好)
- 高竞争: ReentrantLock非公平模式最优
- 公平锁: 性能明显下降(需维护FIFO顺序)
2.3.3 使用场景选择
选择synchronized的场景:
- ✅ 简单的互斥访问
- ✅ 不需要高级特性(中断、超时、公平性)
- ✅ 代码简洁性优先
- ✅ 兼容旧代码
选择ReentrantLock的场景:
- ✅ 需要公平锁(避免线程饥饿)
- ✅ 需要可中断获取(如超时取消)
- ✅ 需要尝试获取(tryLock)
- ✅ 需要多个条件变量(复杂等待通知)
- ✅ 需要锁投票、定时、可中断的锁获取
决策树:
arduino
需要高级特性?
├─ 是 → ReentrantLock
│ ├─ 需要公平性? → new ReentrantLock(true)
│ └─ 性能优先? → new ReentrantLock(false)
└─ 否 → synchronized
2.4 其他AQS同步工具实现原理
2.4.1 CountDownLatch - 倒计数门闩
实现原理:
java
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count); // state=倒计数初始值
}
// 共享式获取:state=0时成功
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
// 共享式释放:递减state
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
if (c == 0)
return false; // 已经为0,无需释放
int nextc = c - 1;
if (compareAndSetState(c, nextc))
return nextc == 0; // 减到0时返回true,触发唤醒
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
// 等待state减到0
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 递减state
public void countDown() {
sync.releaseShared(1);
}
public long getCount() {
return sync.getState();
}
}
关键点:
- state表示倒计数值
await()在state>0时阻塞,state=0时所有线程一起被唤醒countDown()每次递减state,减到0时唤醒所有等待线程- 一次性使用: state减到0后无法重置
2.4.2 CyclicBarrier - 循环屏障
实现原理(基于ReentrantLock + Condition):
java
public class CyclicBarrier {
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
private final int parties; // 参与线程数
private final Runnable barrierCommand; // 屏障动作
private int count; // 剩余等待数
private static class Generation {
boolean broken = false; // 屏障是否被破坏
}
private Generation generation = new Generation();
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
// 到达屏障点并等待
public int await() throws InterruptedException, BrokenBarrierException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
int index = --count;
if (index == 0) { // 最后一个到达
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run(); // 执行屏障动作
ranAction = true;
nextGeneration(); // 开启下一代
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 不是最后一个,循环等待
for (;;) {
try {
trip.await(); // 等待signal
break;
} catch (InterruptedException ie) {
if (g == generation && !g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
}
} finally {
lock.unlock();
}
}
// 开启下一代(重置count,唤醒所有等待线程)
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
}
CountDownLatch vs CyclicBarrier:
| 维度 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 底层实现 | AQS共享模式 | ReentrantLock + Condition |
| 可重用性 | ❌一次性 | ✅可重置 |
| 等待角色 | 一组等一组 | 互相等待 |
| 计数方向 | 递减到0 | 递增到N |
| 屏障动作 | ❌不支持 | ✅支持barrierAction |
| 典型场景 | 主线程等所有子线程完成 | 多线程互相等待同步点 |
2.4.3 Semaphore - 信号量
实现原理:
java
public class Semaphore implements java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits); // state=许可数
}
final int getPermits() {
return getState();
}
// 非公平获取
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 释放许可
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // 溢出检查
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
// 非公平版本
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
// 公平版本
static final class FairSync extends Sync {
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
// 公平性:检查队列中是否有等待线程
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
// 获取许可
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 释放许可
public void release() {
sync.releaseShared(1);
}
}
应用场景:
- 限流(控制并发访问数)
- 资源池(数据库连接池、线程池)
2.4.4 ReadWriteLock - 读写锁
实现原理(state的位运算):
java
abstract static class Sync extends AbstractQueuedSynchronizer {
// state的位划分
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT); // 65536
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 65535
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; // 低16位掩码
// 读锁计数(高16位)
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 写锁计数(低16位)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
// 获取写锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 写锁数
if (c != 0) {
// state!=0,存在读锁或写锁
if (w == 0 || current != getExclusiveOwnerThread())
return false; // 存在读锁或其他线程持有写锁
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires); // 重入
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
// 获取读锁
protected final int tryAcquireShared(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
// 存在写锁且不是当前线程持有
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) { // 高16位+1
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
// 使用ThreadLocal记录每个线程的读锁重入次数
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
}
锁降级机制:
java
// 正确的锁降级示例
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// 需要更新缓存
rwl.readLock().unlock(); // ①释放读锁
rwl.writeLock().lock(); // ②获取写锁
try {
// 再次检查(其他线程可能已更新)
if (!cacheValid) {
data = loadData();
cacheValid = true;
}
// ③锁降级:在持有写锁的情况下获取读锁
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // ④释放写锁
}
}
try {
use(data);
} finally {
rwl.readLock().unlock(); // ⑤释放读锁
}
}
}
为什么没有锁升级?
锁升级(读锁→写锁)会导致死锁:
makefile
线程A: 持有读锁 → 想升级为写锁 → 等待其他读锁释放
线程B: 持有读锁 → 想升级为写锁 → 等待其他读锁释放
→ 死锁!
锁降级(写锁→读锁)是安全的:
css
线程A: 持有写锁 → 获取读锁 → 释放写锁
此时无其他线程持有任何锁,不会死锁
三、实战场景应用
3.1 场景1: 分布式任务协调中的CountDownLatch应用
业务背景
电商平台需要批量导入订单数据,每批次1000个订单。为提高效率,采用多线程并行导入:
- 将1000个订单分成10组,每组100个
- 10个线程并行导入
- 主线程需要等待所有子线程完成后,汇总导入结果并发送通知
方案设计
使用CountDownLatch实现主线程等待:

完整代码实现
java
/**
* 订单批量导入服务
*/
@Service
@Slf4j
public class OrderBatchImportService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private NotificationService notificationService;
private final ExecutorService executor = Executors.newFixedThreadPool(10);
/**
* 批量导入订单
* @param orders 待导入订单列表
* @return 导入结果统计
*/
public ImportResult batchImport(List<OrderDTO> orders) {
int batchSize = 100;
int totalBatches = (orders.size() + batchSize - 1) / batchSize;
// ①创建CountDownLatch
CountDownLatch latch = new CountDownLatch(totalBatches);
// 结果收集器(线程安全)
ConcurrentHashMap<String, AtomicInteger> resultMap = new ConcurrentHashMap<>();
resultMap.put("success", new AtomicInteger(0));
resultMap.put("failed", new AtomicInteger(0));
log.info("开始批量导入订单,总数:{},分{}批", orders.size(), totalBatches);
// ②提交子任务
for (int i = 0; i < totalBatches; i++) {
int fromIndex = i * batchSize;
int toIndex = Math.min(fromIndex + batchSize, orders.size());
List<OrderDTO> batch = orders.subList(fromIndex, toIndex);
executor.submit(() -> {
try {
importBatch(batch, resultMap);
} catch (Exception e) {
log.error("批次导入失败", e);
resultMap.get("failed").addAndGet(batch.size());
} finally {
latch.countDown(); // ③完成后countDown
log.info("批次完成,剩余批次:{}", latch.getCount());
}
});
}
try {
// ④主线程等待所有子线程完成(超时30秒)
boolean finished = latch.await(30, TimeUnit.SECONDS);
if (!finished) {
log.error("批量导入超时,已完成批次:{}", totalBatches - latch.getCount());
throw new BusinessException("批量导入超时");
}
// ⑤汇总结果
ImportResult result = new ImportResult();
result.setTotalCount(orders.size());
result.setSuccessCount(resultMap.get("success").get());
result.setFailedCount(resultMap.get("failed").get());
log.info("批量导入完成:{}", result);
// ⑥发送通知
notificationService.sendImportResult(result);
return result;
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BusinessException("批量导入被中断", e);
}
}
/**
* 导入单个批次
*/
private void importBatch(List<OrderDTO> batch,
ConcurrentHashMap<String, AtomicInteger> resultMap) {
for (OrderDTO orderDTO : batch) {
try {
Order order = convertToEntity(orderDTO);
orderRepository.save(order);
resultMap.get("success").incrementAndGet();
} catch (Exception e) {
log.error("订单导入失败,orderId:{}", orderDTO.getOrderId(), e);
resultMap.get("failed").incrementAndGet();
}
}
}
@Data
public static class ImportResult {
private int totalCount;
private int successCount;
private int failedCount;
public double getSuccessRate() {
return totalCount == 0 ? 0 : (double) successCount / totalCount * 100;
}
}
}
错误处理:子任务异常的兜底方案
问题: 如果子任务抛出异常未捕获,可能导致countDown()未执行,主线程永久阻塞。
解决方案:
java
executor.submit(() -> {
try {
importBatch(batch, resultMap);
} catch (Exception e) {
log.error("批次导入失败", e);
resultMap.get("failed").addAndGet(batch.size());
} finally {
latch.countDown(); // finally确保一定执行
}
});
超时控制
java
// 带超时的await
boolean finished = latch.await(30, TimeUnit.SECONDS);
if (!finished) {
// 超时处理
log.error("部分任务未完成,已完成:{}/{}",
totalBatches - latch.getCount(), totalBatches);
// 取消未完成的任务
executor.shutdownNow();
throw new BusinessException("批量导入超时");
}
3.2 场景2: 使用Semaphore限流
业务背景
电商平台需要限制数据库连接池的并发访问数量:
- 数据库最大连接数20
- 应用服务器有100个工作线程
- 需要控制同时访问数据库的线程数≤20
方案设计
使用Semaphore(20)限制并发:
java
/**
* 基于Semaphore的数据库连接池
*/
@Slf4j
public class DatabaseConnectionPool {
// 实际连接池
private final BlockingQueue<Connection> connections;
// 信号量控制并发数
private final Semaphore semaphore;
private final String jdbcUrl;
private final String username;
private final String password;
public DatabaseConnectionPool(String jdbcUrl, String username, String password,
int maxConnections, boolean fair) {
this.jdbcUrl = jdbcUrl;
this.username = username;
this.password = password;
this.connections = new LinkedBlockingQueue<>(maxConnections);
this.semaphore = new Semaphore(maxConnections, fair); // 公平/非公平可选
// 初始化连接
initConnections(maxConnections);
}
private void initConnections(int count) {
for (int i = 0; i < count; i++) {
try {
Connection conn = DriverManager.getConnection(jdbcUrl, username, password);
connections.offer(conn);
} catch (SQLException e) {
log.error("初始化连接失败", e);
}
}
log.info("数据库连接池初始化完成,连接数:{}", connections.size());
}
/**
* 获取连接(阻塞等待)
*/
public Connection getConnection() throws InterruptedException {
// ①获取许可
semaphore.acquire();
try {
// ②从池中取连接
Connection conn = connections.poll(10, TimeUnit.SECONDS);
if (conn == null) {
throw new RuntimeException("获取连接超时");
}
log.debug("获取连接成功,剩余许可:{}", semaphore.availablePermits());
return conn;
} catch (Exception e) {
// 异常时需要释放许可
semaphore.release();
throw e;
}
}
/**
* 获取连接(超时等待)
*/
public Connection getConnection(long timeout, TimeUnit unit)
throws InterruptedException, TimeoutException {
// ①尝试获取许可(超时)
if (!semaphore.tryAcquire(timeout, unit)) {
throw new TimeoutException("获取连接超时,等待时间:" + timeout + unit);
}
try {
Connection conn = connections.poll(timeout, unit);
if (conn == null) {
throw new TimeoutException("连接池为空");
}
return conn;
} catch (Exception e) {
semaphore.release();
throw e;
}
}
/**
* 释放连接
*/
public void releaseConnection(Connection conn) {
if (conn != null) {
try {
// ①归还连接到池
if (!connections.offer(conn, 5, TimeUnit.SECONDS)) {
log.error("归还连接失败,连接池已满");
conn.close(); // 关闭连接
}
} catch (Exception e) {
log.error("归还连接异常", e);
} finally {
// ②释放许可(必须执行)
semaphore.release();
log.debug("释放连接,剩余许可:{}", semaphore.availablePermits());
}
}
}
/**
* 获取连接池状态
*/
public PoolStats getStats() {
PoolStats stats = new PoolStats();
stats.setMaxConnections(semaphore.availablePermits() + semaphore.getQueueLength());
stats.setAvailableConnections(semaphore.availablePermits());
stats.setWaitingThreads(semaphore.getQueueLength());
stats.setActiveConnections(stats.getMaxConnections() - stats.getAvailableConnections());
return stats;
}
@Data
public static class PoolStats {
private int maxConnections;
private int availableConnections;
private int activeConnections;
private int waitingThreads;
}
}
使用示例
java
@Service
public class OrderService {
@Autowired
private DatabaseConnectionPool connectionPool;
public Order queryOrder(Long orderId) {
Connection conn = null;
try {
// 获取连接(最多等待3秒)
conn = connectionPool.getConnection(3, TimeUnit.SECONDS);
PreparedStatement ps = conn.prepareStatement(
"SELECT * FROM t_order WHERE id = ?");
ps.setLong(1, orderId);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
return mapToOrder(rs);
}
return null;
} catch (TimeoutException e) {
log.error("获取数据库连接超时,orderId:{}", orderId);
throw new BusinessException("系统繁忙,请稍后重试");
} catch (Exception e) {
log.error("查询订单失败,orderId:{}", orderId, e);
throw new BusinessException("查询失败", e);
} finally {
// 必须释放连接
connectionPool.releaseConnection(conn);
}
}
}
公平与非公平的选择
非公平模式(默认):
- 新到达的线程可能直接获取许可,无需排队
- 吞吐量高,但可能导致饥饿
公平模式:
- 严格按照FIFO顺序分配许可
- 避免饥饿,但性能稍差
性能测试对比:
java
@BenchmarkMode(Mode.Throughput)
@State(Scope.Benchmark)
public class SemaphoreBenchmark {
private Semaphore fairSemaphore = new Semaphore(20, true);
private Semaphore unfairSemaphore = new Semaphore(20, false);
@Benchmark
@Threads(100)
public void testFair() throws InterruptedException {
fairSemaphore.acquire();
try {
// 模拟业务操作
Thread.sleep(1);
} finally {
fairSemaphore.release();
}
}
@Benchmark
@Threads(100)
public void testUnfair() throws InterruptedException {
unfairSemaphore.acquire();
try {
Thread.sleep(1);
} finally {
unfairSemaphore.release();
}
}
}
// 结果(ops/s):
// testFair: 12000
// testUnfair: 18000 (提升50%)
选择建议:
- 高吞吐场景 → 非公平模式
- 需要避免饥饿 → 公平模式
3.3 场景3: 使用ReadWriteLock优化缓存
业务背景
电商平台的商品服务需要缓存热门商品信息:
- 缓存读操作占99%,写操作占1%
- 使用
synchronized导致读操作也串行化,性能瓶颈明显
使用synchronized的性能问题
java
/**
* 基于synchronized的缓存(性能差)
*/
public class ProductCacheSynchronized {
private final Map<Long, ProductDTO> cache = new HashMap<>();
// 读操作也需要加锁,导致串行化
public synchronized ProductDTO get(Long productId) {
return cache.get(productId);
}
public synchronized void put(Long productId, ProductDTO product) {
cache.put(productId, product);
}
public synchronized void remove(Long productId) {
cache.remove(productId);
}
}
性能问题:
- 读操作占99%,但也需要获取独占锁
- 100个线程同时读取,也会串行执行
- 吞吐量严重受限
ReadWriteLock改造
java
/**
* 基于ReadWriteLock的缓存(性能优)
*/
@Slf4j
public class ProductCacheReadWriteLock {
private final Map<Long, ProductDTO> cache = new HashMap<>();
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
@Autowired
private ProductRepository productRepository;
/**
* 读取缓存(多线程并发读)
*/
public ProductDTO get(Long productId) {
readLock.lock(); // 读锁
try {
ProductDTO product = cache.get(productId);
if (product != null) {
log.debug("缓存命中,productId:{}", productId);
return product;
}
} finally {
readLock.unlock();
}
// 缓存未命中,需要从数据库加载
return loadFromDB(productId);
}
/**
* 从数据库加载并更新缓存(锁降级)
*/
private ProductDTO loadFromDB(Long productId) {
writeLock.lock(); // ①获取写锁
try {
// ②双重检查(其他线程可能已加载)
ProductDTO product = cache.get(productId);
if (product != null) {
return product;
}
// ③从数据库加载
log.info("从数据库加载商品,productId:{}", productId);
product = productRepository.findById(productId);
if (product != null) {
cache.put(productId, product);
}
return product;
} finally {
writeLock.unlock();
}
}
/**
* 更新缓存(独占写)
*/
public void put(Long productId, ProductDTO product) {
writeLock.lock();
try {
cache.put(productId, product);
log.info("更新缓存,productId:{}", productId);
} finally {
writeLock.unlock();
}
}
/**
* 删除缓存
*/
public void remove(Long productId) {
writeLock.lock();
try {
cache.remove(productId);
log.info("删除缓存,productId:{}", productId);
} finally {
writeLock.unlock();
}
}
/**
* 批量预热缓存(锁降级示例)
*/
public void warmUp(List<Long> productIds) {
writeLock.lock(); // ①获取写锁
try {
log.info("开始预热缓存,商品数:{}", productIds.size());
List<ProductDTO> products = productRepository.findByIds(productIds);
for (ProductDTO product : products) {
cache.put(product.getProductId(), product);
}
// ②锁降级:获取读锁
readLock.lock();
} finally {
writeLock.unlock(); // ③释放写锁
}
try {
// ④持有读锁,允许其他线程并发读取
log.info("缓存预热完成,当前缓存数:{}", cache.size());
} finally {
readLock.unlock(); // ⑤释放读锁
}
}
/**
* 获取缓存统计
*/
public CacheStats getStats() {
readLock.lock();
try {
CacheStats stats = new CacheStats();
stats.setCacheSize(cache.size());
return stats;
} finally {
readLock.unlock();
}
}
@Data
public static class CacheStats {
private int cacheSize;
}
}
性能提升数据对比
测试代码:
java
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 3)
@Measurement(iterations = 5)
@State(Scope.Benchmark)
public class CacheBenchmark {
private ProductCacheSynchronized syncCache;
private ProductCacheReadWriteLock rwCache;
@Setup
public void setup() {
syncCache = new ProductCacheSynchronized();
rwCache = new ProductCacheReadWriteLock();
// 预热缓存
for (long i = 1; i <= 1000; i++) {
ProductDTO product = new ProductDTO();
product.setProductId(i);
product.setProductName("商品" + i);
syncCache.put(i, product);
rwCache.put(i, product);
}
}
// 读多写少场景(99%读,1%写)
@Benchmark
@Threads(100)
public void testSynchronizedRead() {
long productId = ThreadLocalRandom.current().nextLong(1, 1001);
if (ThreadLocalRandom.current().nextInt(100) < 99) {
// 99%读操作
syncCache.get(productId);
} else {
// 1%写操作
ProductDTO product = new ProductDTO();
product.setProductId(productId);
syncCache.put(productId, product);
}
}
@Benchmark
@Threads(100)
public void testReadWriteLockRead() {
long productId = ThreadLocalRandom.current().nextLong(1, 1001);
if (ThreadLocalRandom.current().nextInt(100) < 99) {
rwCache.get(productId);
} else {
ProductDTO product = new ProductDTO();
product.setProductId(productId);
rwCache.put(productId, product);
}
}
}
测试结果(JDK 17, ops/s):
| 并发线程数 | synchronized | ReadWriteLock | 提升倍数 |
|---|---|---|---|
| 10 | 850K | 2.1M | 2.5x |
| 50 | 420K | 3.8M | 9.0x |
| 100 | 280K | 4.2M | 15.0x |
| 200 | 190K | 4.5M | 23.7x |
结论:
- 读多写少场景下,ReadWriteLock性能提升显著
- 并发度越高,性能优势越明显
- 100线程时性能提升15倍
四、生产案例与故障排查
4.1 案例1: AQS在高并发场景的应用
业务场景:秒杀系统库存扣减
电商平台秒杀活动,需要保证:
- 库存扣减的原子性(不能超卖)
- 高并发(QPS 10000+)
- 低延迟(P99 < 50ms)
为什么不用synchronized?
synchronized的问题:
java
// 使用synchronized的库存扣减
public synchronized boolean deductStock(Long skuId, int quantity) {
Integer stock = stockMap.get(skuId);
if (stock == null || stock < quantity) {
return false; // 库存不足
}
stockMap.put(skuId, stock - quantity);
return true;
}
性能瓶颈:
- 锁粒度太粗: 所有SKU共用一把锁
- SKU A的扣减会阻塞SKU B的扣减
- 高并发下吞吐量受限
技术方案:分段锁 + ReentrantLock
java
/**
* 高性能库存扣减服务
*/
@Service
@Slf4j
public class StockDeductionService {
// SKU维度的分段锁(降低锁竞争)
private final ConcurrentHashMap<Long, ReentrantLock> skuLocks = new ConcurrentHashMap<>();
// 库存缓存(Caffeine本地缓存 + Redis分布式缓存)
@Autowired
private Cache<Long, Integer> localCache;
@Autowired
private RedisTemplate<String, Integer> redisTemplate;
/**
* 扣减库存
* @param skuId 商品SKU ID
* @param quantity 扣减数量
* @return 是否扣减成功
*/
public boolean deductStock(Long skuId, int quantity) {
// ①获取SKU维度的锁(分段锁,降低竞争)
ReentrantLock lock = skuLocks.computeIfAbsent(skuId,
k -> new ReentrantLock(false)); // 非公平锁,性能优先
// ②尝试获取锁(超时100ms)
try {
if (!lock.tryLock(100, TimeUnit.MILLISECONDS)) {
log.warn("获取库存锁超时,skuId:{}", skuId);
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
try {
// ③先查本地缓存
Integer stock = localCache.getIfPresent(skuId);
if (stock == null) {
// ④本地缓存未命中,查Redis
String redisKey = "stock:" + skuId;
stock = redisTemplate.opsForValue().get(redisKey);
if (stock == null) {
// ⑤Redis也未命中,查数据库
stock = loadStockFromDB(skuId);
if (stock == null) {
return false; // 商品不存在
}
// 回写缓存
redisTemplate.opsForValue().set(redisKey, stock, 60, TimeUnit.SECONDS);
}
// 回写本地缓存
localCache.put(skuId, stock);
}
// ⑥检查库存
if (stock < quantity) {
log.warn("库存不足,skuId:{},当前库存:{},扣减数量:{}",
skuId, stock, quantity);
return false;
}
// ⑦扣减库存(本地缓存 + Redis)
int newStock = stock - quantity;
localCache.put(skuId, newStock);
String redisKey = "stock:" + skuId;
redisTemplate.opsForValue().set(redisKey, newStock, 60, TimeUnit.SECONDS);
// ⑧异步更新数据库(MQ)
sendStockUpdateMessage(skuId, newStock);
log.info("库存扣减成功,skuId:{},扣减:{},剩余:{}", skuId, quantity, newStock);
return true;
} finally {
// ⑨释放锁(必须在finally中)
lock.unlock();
}
}
/**
* 从数据库加载库存
*/
private Integer loadStockFromDB(Long skuId) {
// 实际实现省略
return 1000;
}
/**
* 异步更新数据库库存(通过MQ)
*/
private void sendStockUpdateMessage(Long skuId, int newStock) {
// 发送MQ消息,异步更新数据库
// 实际实现省略
}
/**
* 定时清理空闲锁对象(防止内存泄漏)
*/
@Scheduled(fixedRate = 60000)
public void cleanIdleLocks() {
skuLocks.entrySet().removeIf(entry -> {
ReentrantLock lock = entry.getValue();
// 尝试获取锁,获取成功说明无人使用
if (lock.tryLock()) {
try {
return true; // 移除
} finally {
lock.unlock();
}
}
return false; // 保留
});
log.info("清理空闲锁完成,当前锁数量:{}", skuLocks.size());
}
}
压测数据与优化过程
优化前(synchronized):
makefile
并发:1000
QPS: 2800
P99延迟: 350ms
错误率: 15% (超时)
优化后(ReentrantLock分段锁):
makefile
并发:1000
QPS: 12000 (提升4.3倍)
P99延迟: 45ms (降低87%)
错误率: 0.1%
优化关键点:
- 分段锁: 每个SKU独立锁,降低竞争
- 非公平锁: 性能优先,避免线程切换
- 超时机制 :
tryLock(100ms),避免无限等待 - 多级缓存: 本地缓存(Caffeine) + Redis,降低DB压力
- 异步更新DB: 通过MQ异步,不阻塞主流程
4.2 案例2: CountDownLatch超时未释放导致的线程泄漏
故障现象
生产环境发现线程池线程数持续增长,最终耗尽:
arduino
[ERROR] ThreadPoolExecutor: Worker creation failed
[WARN] ThreadPoolExecutor: Pool size: 200/200 (max)
问题分析
排查过程:
- 线程dump分析
bash
jstack <pid> > thread_dump.txt
发现大量线程阻塞在CountDownLatch.await():
php
"batch-import-1" #123 prio=5 os_prio=0 waiting on condition
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly
at java.util.concurrent.CountDownLatch.await(CountDownLatch.java:231)
at com.example.service.BatchService.importData(BatchService.java:45)
- 代码审查
发现问题代码:
java
// ❌错误代码
public void batchImport(List<Data> dataList) {
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
processData(dataList.get(i)); // 可能抛异常
latch.countDown(); // 异常时未执行!
});
}
latch.await(); // 永久阻塞!
}
根因:
- 子任务抛出异常时,
countDown()未执行 - 主线程
await()永久阻塞 - 线程池线程被占用,最终耗尽
解决方案
方案1: finally块保证countDown
java
// ✅正确代码
public void batchImport(List<Data> dataList) {
CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
final int index = i;
executor.submit(() -> {
try {
processData(dataList.get(index));
} catch (Exception e) {
log.error("处理失败,index:{}", index, e);
} finally {
latch.countDown(); // 必定执行
}
});
}
// 增加超时保护
boolean finished = latch.await(30, TimeUnit.SECONDS);
if (!finished) {
log.error("批量处理超时");
throw new BusinessException("批量处理超时");
}
}
方案2: 使用CompletableFuture(推荐)
java
// ✅更优方案:使用CompletableFuture
public void batchImport(List<Data> dataList) {
// 创建异步任务
List<CompletableFuture<Void>> futures = dataList.stream()
.map(data -> CompletableFuture.runAsync(() -> {
processData(data);
}, executor))
.collect(Collectors.toList());
// 等待所有任务完成(带超时)
try {
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.get(30, TimeUnit.SECONDS);
log.info("批量处理完成");
} catch (TimeoutException e) {
log.error("批量处理超时");
// 取消未完成的任务
futures.forEach(f -> f.cancel(true));
throw new BusinessException("批量处理超时", e);
} catch (Exception e) {
log.error("批量处理失败", e);
throw new BusinessException("批量处理失败", e);
}
}
CompletableFuture的优势:
- ✅ 异常自动传播,不会丢失
- ✅ 支持链式调用,代码更优雅
- ✅ 支持超时取消
- ✅ 支持组合多个异步任务
改进方案对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| CountDownLatch + finally | 简单直接 | 异常处理复杂,需手动管理 |
| CompletableFuture | 异常自动处理,API丰富 | 学习成本稍高 |
| CompletableFuture + 超时 | 完善的异常和超时处理 | - |
五、常见问题与避坑指南
5.1 AQS的state字段为什么用volatile?
原因1: 保证可见性
java
// 线程A
state = 1; // 释放锁
// 线程B
if (state == 0) { // 获取锁
// ...
}
如果state不是volatile,线程B可能读取到旧值(缓存),导致:
- 看到state=0(实际已被修改为1)
- 重复获取锁,破坏互斥性
原因2: 配合CAS保证原子性
java
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
CAS操作本身是原子的,但需要volatile保证:
- 读取最新值进行比较
- 修改后立即对其他线程可见
原因3: 禁止重排序
java
// 获取锁的典型流程
if (compareAndSetState(0, 1)) { // ①CAS获取锁
setExclusiveOwnerThread(current); // ②设置持有线程
// ③执行临界区代码
}
volatile的内存屏障保证:
- ①②不会重排序到③之后
- 其他线程看到state=1时,必定也能看到owner线程的设置
5.2 为什么需要CLH队列?直接自旋不行吗?
纯自旋的问题:
java
// 纯自旋锁
public class SpinLock {
private AtomicBoolean locked = new AtomicBoolean(false);
public void lock() {
while (!locked.compareAndSet(false, true)) {
// 不断自旋,浪费CPU
}
}
}
问题:
- CPU空转: 高竞争下,大量线程自旋,CPU利用率100%但无效
- 不公平: 无法保证FIFO顺序,可能饥饿
- 无法支持中断/超时: 自旋过程无法响应中断
CLH队列的优势:
- 自旋+阻塞结合: 先自旋几次,失败后阻塞(park),避免CPU空转
- FIFO公平性: 队列天然保证先进先出
- 支持中断/超时 :
parkNanos/parkUntil支持超时,park可被中断 - 节点状态管理 :
waitStatus记录节点状态,优化唤醒流程
5.3 CountDownLatch vs CyclicBarrier如何选择?
CountDownLatch场景:
- ✅ 一组线程等待另一组线程完成
- ✅ 一次性使用(无需重置)
- ✅ 等待者与执行者角色明确
示例: 主线程等待所有子线程完成
java
CountDownLatch latch = new CountDownLatch(10);
// 主线程
latch.await();
// 子线程
latch.countDown();
CyclicBarrier场景:
- ✅ 多个线程相互等待
- ✅ 需要重复使用
- ✅ 到达屏障点后执行共同动作
示例: 多线程计算,每轮同步
java
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("一轮计算完成");
});
// 每个线程
for (int round = 0; round < 10; round++) {
compute(round);
barrier.await(); // 等待其他线程
}
决策表:
| 需求 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 重复使用 | ❌ | ✅ |
| 角色分离(等待者vs执行者) | ✅ | ❌ |
| 屏障动作 | ❌ | ✅ |
| 底层实现 | AQS | ReentrantLock |
5.4 ReentrantLock为什么推荐在finally中unlock?
错误示例:
java
// ❌错误:unlock不在finally中
public void transfer(Account from, Account to, int amount) {
lock.lock();
if (from.getBalance() < amount) {
lock.unlock(); // ①提前返回时释放
throw new InsufficientBalanceException();
}
from.deduct(amount);
to.add(amount); // ②如果这里抛异常,锁永不释放!
lock.unlock(); // ③正常流程释放
}
问题:
- 如果②抛出异常,③不会执行,锁永不释放
- 其他线程永久阻塞,导致死锁
正确示例:
java
// ✅正确:unlock在finally中
public void transfer(Account from, Account to, int amount) {
lock.lock();
try {
if (from.getBalance() < amount) {
throw new InsufficientBalanceException();
}
from.deduct(amount);
to.add(amount);
} finally {
lock.unlock(); // 无论如何都会释放
}
}
原则:
lock()和unlock()必须配对unlock()必须在finally块中- 避免在
try块外lock()(可能异常导致未lock就unlock)
5.5 Semaphore的公平与非公平模式性能差异多大?
前面的测试结果显示:
- 非公平模式吞吐量提升约50%
- 公平模式延迟更稳定,避免饥饿
适用场景:
- 高吞吐业务: 非公平模式(如接口限流)
- 关键业务: 公平模式(如任务调度,避免饥饿)
5.6 ReadWriteLock的锁降级是什么?如何实现?
锁降级: 持有写锁时获取读锁,然后释放写锁
正确示例:
java
rwLock.writeLock().lock(); // ①获取写锁
try {
// 更新数据
data = newData;
rwLock.readLock().lock(); // ②获取读锁(锁降级)
} finally {
rwLock.writeLock().unlock(); // ③释放写锁
}
try {
// ④使用数据(持有读锁)
use(data);
} finally {
rwLock.readLock().unlock(); // ⑤释放读锁
}
为什么需要锁降级?
- 保证数据一致性: ②③之间,其他线程无法修改数据
- 提高并发性: 释放写锁后,其他线程可以并发读
5.7 为什么没有锁升级?
锁升级(读锁→写锁)会导致死锁:
java
// ❌死锁示例
// 线程A
readLock.lock();
// ... 想升级为写锁
writeLock.lock(); // 阻塞,等待读锁释放
// 线程B
readLock.lock();
// ... 想升级为写锁
writeLock.lock(); // 阻塞,等待读锁释放
// 死锁! 两个线程都持有读锁,都等待对方释放
正确做法: 先释放读锁,再获取写锁
java
// ✅正确
readLock.lock();
try {
// 读取数据
if (needUpdate) {
readLock.unlock(); // ①释放读锁
writeLock.lock(); // ②获取写锁
try {
// ③双重检查
if (needUpdate) {
// 更新数据
}
} finally {
writeLock.unlock();
}
}
} finally {
if (readLock.isHeldByCurrentThread()) {
readLock.unlock();
}
}
5.8 Condition vs wait/notify的区别?
| 特性 | wait/notify | Condition |
|---|---|---|
| 绑定对象 | 任意Object | Lock |
| 条件数量 | 1个(对象监视器) | 多个(一个Lock多个Condition) |
| 唤醒精确性 | ❌notify随机唤醒 | ✅signal精确唤醒 |
| 支持中断 | ✅ | ✅ |
| 支持超时 | ✅ | ✅ |
| 必须在同步块 | ✅必须在synchronized | ✅必须持有Lock |
| 虚假唤醒 | ⚠️存在 | ⚠️存在 |
Condition的优势:
java
// 多条件示例:生产者-消费者
Lock lock = new ReentrantLock();
Condition notFull = lock.newCondition(); // 条件1
Condition notEmpty = lock.newCondition(); // 条件2
// 生产者
lock.lock();
try {
while (count == capacity) {
notFull.await(); // 等待notFull条件
}
// 生产
notEmpty.signal(); // 精确唤醒消费者
} finally {
lock.unlock();
}
// 消费者
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待notEmpty条件
}
// 消费
notFull.signal(); // 精确唤醒生产者
} finally {
lock.unlock();
}
六、最佳实践与总结
6.1 如何选择合适的同步工具?
决策树:
arduino
需要互斥访问?
├─ 是 → 需要高级特性(中断/超时/公平/多条件)?
│ ├─ 是 → ReentrantLock
│ └─ 否 → synchronized
└─ 否 → 需要什么样的协调?
├─ 限制并发数 → Semaphore
├─ 等待多个线程完成 → CountDownLatch
├─ 多线程互相等待同步点 → CyclicBarrier
├─ 读多写少 → ReadWriteLock
└─ 生产者-消费者 → BlockingQueue
6.2 AQS自定义同步器的实现步骤
步骤1: 继承AQS
java
public class MyLock {
private static class Sync extends AbstractQueuedSynchronizer {
// 实现同步逻辑
}
private final Sync sync = new Sync();
}
步骤2: 定义state语义
- 独占锁: state=0未锁,state=1已锁
- 共享锁: state=剩余许可数
- 信号量: state=可用资源数
步骤3: 实现tryAcquire/tryRelease(独占模式)
java
protected boolean tryAcquire(int arg) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
步骤4: 实现tryAcquireShared/tryReleaseShared(共享模式)
java
protected int tryAcquireShared(int arg) {
for (;;) {
int available = getState();
int remaining = available - arg;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
protected boolean tryReleaseShared(int arg) {
for (;;) {
int current = getState();
int next = current + arg;
if (compareAndSetState(current, next))
return true;
}
}
步骤5: 提供Lock API
java
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
6.3 同步工具使用原则
-
优先使用高级工具
- 使用
java.util.concurrent包提供的工具 - 避免直接使用
wait/notify
- 使用
-
锁的粒度要合适
- 锁粒度太粗 → 并发度低
- 锁粒度太细 → 复杂度高,容易死锁
-
避免锁嵌套
- 容易死锁
- 难以维护
-
及时释放锁
- 锁保护的临界区尽量小
unlock()必须在finally块
-
优先使用非阻塞算法
- CAS、原子类、无锁数据结构
- 性能更好,无死锁风险
6.4 性能优化建议
-
减少锁竞争
- 缩小临界区
- 锁分段(ConcurrentHashMap)
- 读写分离(ReadWriteLock)
-
选择合适的锁类型
- 低竞争 → synchronized
- 高竞争 → ReentrantLock(非公平)
- 读多写少 → ReadWriteLock
-
避免锁的不必要获取
- 双重检查锁定(DCL)
tryLock()尝试获取
-
使用并发容器
ConcurrentHashMap代替HashtableCopyOnWriteArrayList代替Vector
6.5 常见陷阱总结
| 陷阱 | 后果 | 避免方法 |
|---|---|---|
| unlock()不在finally | 锁泄漏,死锁 | 必须在finally中unlock |
| CountDownLatch未countDown | 线程永久阻塞 | finally保证countDown |
| Semaphore获取后未释放 | 资源泄漏 | finally保证release |
| 锁重入未正确计数 | 锁提前释放 | 使用ReentrantLock |
| 读锁升级为写锁 | 死锁 | 先释放读锁再获取写锁 |
| synchronized(this) | 锁粗粒度 | 使用私有锁对象 |
| wait()不在while循环 | 虚假唤醒 | 用while检查条件 |
6.6 总结
AQS是Java并发编程的基石,深入理解AQS能够:
- 掌握JUC核心工具原理: ReentrantLock、Semaphore、CountDownLatch等
- 提升并发编程能力: 选择合适的同步工具,避免常见陷阱
- 优化系统性能: 根据场景选择最优方案,如读写锁、分段锁等
- 自定义同步器: 在特殊场景下实现定制化同步逻辑
核心要点回顾:
- AQS核心: state(同步状态) + CLH队列(等待线程) + CAS(原子操作)
- 独占vs共享: 独占一次唤醒一个,共享传播唤醒
- Condition: 一个Lock多个条件,精确唤醒
- ReentrantLock vs synchronized: 功能丰富 vs 简洁易用
- 读写锁: 读多写少场景性能提升显著
- 最佳实践: 锁粒度适中、及时释放、避免嵌套、优先非阻塞
掌握AQS,你已经迈入Java并发编程高级阶段!