并发的本质、内存模型、控制哲学、性能与伸缩性、测试与反模式等原理,见母篇 /编程语言/并发编程.html。本文聚焦 Java/JVM 的具体落地与独有机制。
Java 把母篇所说的"协调机制"落地为一个分层栈,上层依赖下层:
并发设计模式(实例封闭、委托、状态依赖…)
↓
JUC 抽象(Lock、Semaphore、CyclicBarrier、CountDownLatch)
↓
同步器底座(AQS)+ 等待通知(wait/notify、Condition)
↓
硬件(内存屏障、CAS / CompareAndSwap)
AQS(AbstractQueuedSynchronizer) 是 JUC 的统一底座:用一个 volatile int state + CLH 等待队列,把"获取 / 释放"抽象为模板方法。ReentrantLock、Semaphore、CountDownLatch 都只是对 state 语义的不同解释。理解 AQS,等于理解半个 JUC。
母篇讲清了 happens-before 的原理;Java 把它落为三个关键字,各自建立不同的 hb 边:
| 关键字 | 建立的保证 | happens-before 规则 |
|---|---|---|
synchronized |
互斥 + 可见性 + 有序性 | 解锁 hb 后续加锁 |
volatile |
可见性 + 有序性(非原子) | 写 hb 后续读 |
final |
安全发布的初始化可见性 | 构造结束 freeze,正确发布后可见 |
完整的 JMM happens-before 规则(程序顺序、线程启动 / 终止、中断等)见 /编程语言/JAVA/JVM/JAVA内存模型.html。
母篇的设计模式是通用的(Immutable、Thread Confinement…);Java 实践层有三个更具体的惯用法:
| 模式 | 做法 | 关键约束 |
|---|---|---|
| 实例封闭 | 把非线程安全对象包进受控边界,对外只暴露安全接口 | 并发策略集中在一处,最稳健 |
| 线程安全委托 | 把安全责任交给底层线程安全组件(如 ConcurrentHashMap) | 不得引入跨组件的复合状态——"多个原子操作组合后不再原子" |
| 状态依赖操作 | 操作依赖某状态条件成立 | 条件检查 → 等待 → 唤醒后重校验 |
对象安全发布的手段(静态初始化 / final / volatile / 锁)及其 hb 机制见母篇「安全发布」。
等待不是睡眠,而是条件未满足时让出执行权、等待通知。
wait/notify 三原则(违反即 Bug):
Condition(显式条件队列) 相比 wait/notify 的增量:多条件分离、精准唤醒、可中断 / 可超时等待。它是状态机式并发设计的基础设施。
Java 没有"强杀线程"——生命周期管理是一套协作协议:
活跃性失效的原理(死锁 / 活锁 / 饥饿、Coffman 条件)见母篇「进度协调失效」。Java 层的预防手段:
| 问题 | Java 预防手段 |
|---|---|
| 死锁 | 固定全局加锁顺序、tryLock(timeout) 超时放弃、一次性申请、缩小临界区 |
| 活锁 | 随机退避(重试间隔随机化)、限制重试次数、引入协调者 |
| 饥饿 | 公平锁 new ReentrantLock(true)、避免优先级反转、动态优先级提升 |
预防优于检测,设计优于补救。
JVM 对 synchronized 做了一套自动优化,核心是按竞争程度逐级升级,避免无谓的重量级开销:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
(无竞争) (轻度CAS竞争) (重度竞争挂起)
| 优化 | 作用 |
|---|---|
| 偏向锁 | 无竞争时偏向首个线程,省去 CAS(注:JDK 15 起默认废弃,JEP 374) |
| 轻量级锁 | 轻度竞争用 CAS 自旋,不挂起线程 |
| 自旋 / 自适应自旋 | 短暂忙等避免上下文切换;自适应按历史动态调整自旋次数 |
| 锁消除 | JIT 判定不可能竞争(如未逸出的局部对象)直接去锁 |
| 锁粗化 | 把连续的细粒度加锁合并,减少反复加解锁 |
前提:这些优化只能锦上添花,救不了本身不合理的并发设计。