文章目录
🔒《并发编程必考:synchronized 与 AQS 原理》
适合读者:
Java 多线程基础已扎实(Thread、Runnable),
熟悉synchronized
、ReentrantLock
等同步机制,
想深度理解synchronized
底层与 AQS 原理。
1️⃣ 引言:从线程安全谈起
想象一下:
- 银行转账:两个线程同时修改同一账户余额,若不加锁,可能出现“丢钱”问题。
- 火车站售票:多窗口售票,若没有同步机制,可能超卖或重复分配同一票。
线程安全 关键词:
- 临界区(Critical Section)
- 原子性(Atomicity)
- 可见性(Visibility)
- 有序性(Ordering)
要保证这些特性,Java 提供了多种同步机制,今天重点讲解:
- JVM 内置的
synchronized
- Java 层基于 AQS(AbstractQueuedSynchronizer)的锁
2️⃣ synchronized 的原理剖析
2.1 使用方式
public class TicketSeller {
private int tickets = 100;
// 1. 实例方法加锁
public synchronized void sell() {
if (tickets > 0) tickets--;
}
// 2. 静态方法加锁(锁 Class 对象)
public static synchronized void staticSell() { … }
// 3. 代码块加锁
public void sellBlock() {
synchronized(this) {
if (tickets > 0) tickets--;
}
}
}
日志示例:
Thread-1 获取锁
Thread-2 等待锁
Thread-1 卖出 1 张票
Thread-1 释放锁
Thread-2 获取锁
2.2 底层实现(JVM 层面)
synchronized 依赖 JVM 对象头的 Mark Word,分三个阶段:
- 偏向锁
- 轻量级锁(使用 CAS 自旋)
- 重量级锁(进入 OS 阻塞队列)
偏向锁 → 轻量级锁 → 重量级锁
│ │ │
无竞争 少量竞争 高度竞争
图解锁升级流程
Thread A first acquires → Mark Word 标记为偏向 A
Thread B blocks → 偏向锁撤销 → 两者都尝试 CAS(轻量级锁)
多次 CAS 失败 → 重量级锁,OS 阻塞队列
可用 jinfo -flag UseBiasedLocking、jstack 观察锁状态。
3️⃣ AQS:AbstractQueuedSynchronizer 原理详解
3.1 概念入门
AQS 是 JDK 提供的同步器框架,封装了独占锁(ReentrantLock)与共享锁(Semaphore、CountDownLatch)的队列管理逻辑。
底层使用 CLH 队列(虚拟节点链表),保证线程按 FIFO 排队。
3.2 核心机制解析
- state:原子整型,表示锁状态
- CLH 队列:Node 双向链表管理等待线程
加锁流程(独占模式)
acquire(arg):
if tryAcquire(arg) succeed → 返回
else
node = addWaiter(EXCLUSIVE)
parkAndCheckInterrupt(node)
- tryAcquire:子类实现竞争逻辑(CAS 增加 state)
- addWaiter:尾部插入等待队列
- park:阻塞当前线程
解锁流程:
release(arg):
if tryRelease(arg) → unparkSuccessor(head)
- tryRelease:CAS 减少 state
- 唤醒队列头后继线程
图示:线程排队与唤醒
[head] → [Thread A] → [Thread B] → [Thread C]
unlock() → 唤醒 A → A 成功加锁 → B、C 继续等待
3.3 自定义锁案例
public class SimpleLock extends AbstractQueuedSynchronizer {
@Override
protected boolean tryAcquire(int arg) {
return compareAndSetState(0, 1);
}
@Override
protected boolean tryRelease(int arg) {
setState(0);
return true;
}
public void lock() { acquire(1); }
public void unlock() { release(1); }
}
4️⃣ synchronized vs AQS 对比分析
对比项 | synchronized | AQS(如 ReentrantLock) |
---|---|---|
实现方式 | JVM 内置、对象头 Mark Word | Java 层 CAS + CLH 队列 |
是否可中断 | 否 | 是 (lockInterruptibly ) |
公平性 | 否 | 可选公平/非公平 |
尝试获取锁 | 否 | 是 (tryLock ) |
性能 | JDK1.6+ 优化显著 | 灵活,适合复杂场景 |
5️⃣ 常见面试高频问答
- synchronized 如何实现可重入?
Mark Word 中记录拥有者线程和重入计数。 - AQS 为什么使用 CLH 队列?
无需额外锁,链表节点自带排队信息,性能更好。 - AQS 为何用 CAS,而不直接 synchronized?
避免内核阻塞,提升并发性能。 - ReentrantLock 怎么实现公平?
构造时传入 true,tryAcquire 会检查队列头。
6️⃣ 总结与建议
- 简单同步场景,首选 synchronized(语法简洁,JVM 已高度优化)。
- 需可中断、公平锁、超时获取等功能时,使用 AQS(ReentrantLock、Semaphore)。
- 深入理解 AQS,才能自己实现高性能并发组件。
🎓 掌握 synchronized 与 AQS,才能在高并发世界如鱼得水!