
gcz完整示例:从源码看Java并发控制底层逻辑
看了一堆教程还是不会写项目?别慌,这太正常了。很多开发者卡在“懂原理但写不出”,就是因为只看了零散知识点,没啃过核心源码。今天这篇 gcz完整示例,直接带你拆解 Java 并发控制中的核心机制,用真实源码和 完整示例 把逻辑掰碎揉烂。
入口定位:从 AQS 看并发控制核心
在 Java 并发编程中,java.util.concurrent.locks 包是绕不开的核心。所有高性能锁、同步器都建立在 AbstractQueuedSynchronizer (AQS) 之上。
官方源码仓库 中,AQS 的 java.util.concurrent.locks.AbstractQueuedSynchronizer 类是整个并发包的基石。它通过一个 volatile int state 字段表示同步状态,配合 FIFO 等待队列管理竞争者。
很多教程只告诉你“ReentrantLock 比 synchronized 强”,但不说为什么。核心差异就在 AQS 的实现上。synchronized 是 JVM 内置锁,依赖对象头 Mark Word;而 ReentrantLock 基于 AQS,是用户态实现,提供了更丰富的功能(公平锁、可中断、多条件变量)。
核心片段:AQS 核心方法逐行解析
下面这段代码来自 官方源码仓库 中 AQS 的 tryAcquire 和 acquire 方法,是理解锁获取流程的关键:
// java.util.concurrent.locks.AbstractQueuedSynchronizer.java
// 尝试获取锁,非阻塞
protected boolean tryAcquire(int arg) {
// 1. 获取当前线程
final Thread current = Thread.currentThread();
// 2. 获取当前状态(即持有锁的线程数,0表示无锁)
int c = getState();
// 3. 判断锁是否可用(state==0)
if (c == 0) {
// 4. 尝试将 state 原子性地设置为 1
if (!isHeldExclusively(current)
compareAndSetState(c, c + 1)) {
// 5. 设置独占持有线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// 6. 如果当前线程已持有锁,增加重入计数
else if (current == getExclusiveOwnerThread()) {
int nextc = c + 1;
// 7. 检查是否溢出
if (nextc 0) throw new Error(Maximum lock count exceeded);
setState(nextc);
return true;
}
// 8. 获取失败
return false;
}
// 获取锁,可能阻塞
public final void acquire(int arg) {
// 1. 如果 tryAcquire 失败且线程被中断
if (!tryAcquire(arg)
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 2. 自中断(恢复中断状态)
selfInterrupt();
}
逐行讲解:
第 3-8 行:这是非阻塞获取逻辑。compareAndSetState 是 CAS 操作,确保线程安全。只有 state 为 0 时才能获取锁。
第 9-14 行:重入逻辑。如果当前线程已持有锁,直接增加 state 计数,不阻塞。
第 17-21 行:acquire 是阻塞入口。先尝试非阻塞获取,失败则将当前线程封装为 Node 加入等待队列,并进入 acquireQueued 自旋+阻塞逻辑。
设计思想:状态机 + FIFO 队列
AQS 的设计精髓在于 “状态机 + 双向队列”:
状态机:state 字段用 int 表示同步状态。ReentrantLock 中 state 表示重入次数;Semaphore 中 state 表示剩余许可数;CountDownLatch 中 state 表示计数值。一个 int 字段,支撑了多种同步器,这就是 AQS 的抽象能力。
FIFO 双向队列:竞争失败的线程被封装成 Node,入队等待。队列头节点是虚拟头节点(dummy head),不携带线程信息。新节点入队时,如果前驱是头节点,会尝试 CAS 获取锁;失败则挂起等待前驱唤醒。
关键设计决策:
为什么用双向队列? 释放锁时,需要唤醒后继节点,双向链表可以 O(1) 找到后继。
为什么头节点是虚拟的? 避免对真实节点的修改,简化逻辑。头节点永远不携带线程,释放锁时只需修改头指针。
为什么用 CLH 变体? CLH 队列是经典的同步队列,AQS 在其基础上增加了虚拟头节点和信号量机制,减少了不必要的 CAS 竞争。
手写简化版:实现一个可重入锁
下面是一个 完整示例,用 AQS 思想手写一个简化版可重入锁,帮助你理解核心逻辑:
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
// 简化版可重入锁,基于 AQS
public class SimpleReentrantLock implements Lock {
private final Sync sync = new Sync();
// 自定义 Sync,继承 AQS
private static class Sync extends AbstractQueuedSynchronizer {
// 非公平获取:直接 CAS
@Override
protected boolean tryAcquire(int arg) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (current == getExclusiveOwnerThread()) {
setState(c + 1);
return true;
}
return false;
}
// 释放锁
@Override
protected boolean tryRelease(int arg) {
int c = getState() - 1;
if (Thread.currentThread() != getExclusiveOwnerThread()) {
throw new IllegalMonitorStateException();
}
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
// 是否独占持有
@Override
protected boolean isHeldExclusively() {
return getState() != 0
Thread.currentThread() == getExclusiveOwnerThread();
}
}
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void unlock() {
sync.release(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long timeout, java.util.concurrent.TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
// 测试类
public class TestSimpleLock {
public static void main(String[] args) {
SimpleReentrantLock lock = new SimpleReentrantLock();
// 线程1获取锁
new Thread(() - {
lock.lock();
try {
System.out.println(Thread1 acquired lock);
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println(Thread1 released lock);
}
}).start();
// 线程2尝试获取锁
Thread.sleep(100);
new Thread(() - {
if (lock.tryLock()) {
System.out.println(Thread2 acquired lock);
lock.unlock();
} else {
System.out.println(Thread2 failed to acquire lock);
}
}).start();
}
}
关键点:
tryAcquire:核心是非阻塞获取逻辑,必须正确实现重入判断。
tryRelease:必须检查释放线程是否为持有线程,避免误释放。
isHeldExclusively:用于判断锁是否被独占持有,AQS 内部多处调用。
应用场景:生产环境避坑指南
在实际项目中,AQS 相关组件的应用场景非常多,但也容易踩坑:
高并发场景选择:
低竞争:synchronized 足够,JVM 优化后性能接近 AQS 锁。
高竞争:ReentrantLock + 公平模式,减少线程饥饿。
读写分离:ReentrantReadWriteLock,读多写少场景性能提升显著。
常见坑点:
忘记 unlock:必须在 finally 块中释放,否则死锁。
可中断锁误用:lockInterruptibly 可能抛出 InterruptedException,需正确处理。
条件变量混用:多个 Condition 对象需与 lock() 配套使用,避免状态不一致。
性能调优:
避免在锁内做耗时操作,缩小临界区。
高并发场景考虑分段锁(如 ConcurrentHashMap 的 CAS + synchronized 组合)。
监控锁竞争:通过 ThreadMXBean 或 JMX 查看锁等待时间。
实战建议:
在项目中,不要盲目替换 synchronized 为 ReentrantLock。先通过压测确认瓶颈,再选择合适工具。AQS 的复杂性也意味着调试难度更高,务必做好日志和监控。
总结与互动
通过拆解 AQS 核心源码,我们看到了 Java 并发控制的底层逻辑:状态机 + FIFO 队列 + CAS。这套设计不仅支撑了 ReentrantLock,还衍生出 Semaphore、CountDownLatch 等多种同步器。
完整示例 的价值在于:它不是让你背诵 API,而是让你理解“为什么这样设计”。下次遇到并发问题,你能从源码层面分析,而不是盲目调参。
还有什么不懂的?评论区留言挨个回。 特别是关于 AQS 队列操作、公平锁实现细节,或者你在项目中遇到的并发难题,都可以聊。