Java自1996年诞生以来,其并发安全方面演进史,就是一部从"悲观互斥"向"乐观并行"持续进化的历史。本文将沿着JDK版本发布的时间线,梳理Java在解决线程安全问题时,在锁机制、同步工具以及无锁算法上的关键变革。
第一章:混沌初开------JDK 1.0的原始线程模型(1996年)

1996年1月,JDK 1.0的发布奠定了Java最基础的线程模型。这个版本虽然功能原始,但确立了一个具有传承性的设计------Java采用了协作式的线程通信方式。
1.1 基础原语:synchronized与wait/notify
从第一个版本开始,Java就引入了synchronized关键字作为内置锁(Intrinsic Lock),以及与之配套的wait()/notify()方法进行线程间通信。其设计核心非常简洁:
- 非静态方法 使用
synchronized修饰,相当于锁住当前实例对象(synchronized(this))。 - 静态方法 使用
synchronized修饰,相当于锁住该类的Class对象(synchronized(XXX.class))。
这是Java并发安全的起点------悲观锁思想的体现:线程进入同步块前必须获得锁,其他线程只能阻塞等待。在当时的技术背景下,这种设计简单直接,足以应对基础的多线程需求。
1.2 早期设计的遗憾:被废弃的"暴力"方法
JDK 1.0初期还提供了stop()、suspend()和resume()三个方法。但由于它们本质上是不安全的------stop()直接终止线程会导致对象状态被破坏,suspend()挂起线程时持有锁极易引发死锁------这些方法在JDK 1.2中就被正式废弃了。这一事件给Java开发者上了一课:并发控制不能靠"暴力中断",而需要线程间的自觉配合。
第二章:理论奠基------JDK 1.2到1.4的修修补补(1998-2002年)
在Java诞生的前六年,开发者们饱受并发问题的困扰,却往往归咎于代码逻辑,而忽视了更深层的原因:Java内存模型(JMM)的定义存在缺陷。
2.1 可见性难题
早期的JMM缺乏严谨的"先行发生"(happens-before)规则。一个线程修改了共享变量,另一个线程可能永远看不到这个修改,这就是可见性问题。
当时volatile关键字虽然被设计用来解决这个问题,但在旧的JMM下,volatile的语义并不足够强,不能完全解决这个问题。
2.2 民间力量的酝酿:Doug Lea的贡献
在这一时期,并发编程领域的泰斗Doug Lea开始撰写一系列并发工具类,包括后来成为JUC核心的ReentrantLock、ConcurrentHashMap等。这些代码虽然没有被纳入JDK,但其设计思想和实现方案为后来的标准化奠定了基础。
这个时期的Java处于一个尴尬的境地:硬件已经进入多核时代,但语言层面的并发模型还停留在单核时代。
第三章:革命前夜------JDK 1.5的里程碑式突破(2004年)

2004年,JDK 1.5(即Java 5)的发布堪称Java并发编程史上最重要的里程碑。这一年,JSR 133规范为Java带来了全新的内存模型,JSR 166引入了划时代的java.util.concurrent包。
3.1 JSR 133的救赎:内存模型的重新定义
JSR 133重新定义了volatile和synchronized的语义,明确了happens-before关系:
对一个
volatile变量的写操作,happens-before于后续对这个变量的读操作。
这意味着只要写入volatile变量,就相当于向内存发出"刷新"信号;读取volatile变量,则相当于从内存中"加载"最新值。这一修订,为后来高性能并发工具的发展扫清了理论障碍。
3.2 JSR 166的降临:JUC包的诞生
由Doug Lea主导的JSR 166专家组正式将一系列并发工具纳入JDK标准库,包括:
- 显式锁 :
ReentrantLock、ReadWriteLock - 同步器 :
Semaphore、CountDownLatch、CyclicBarrier - 并发容器 :
ConcurrentHashMap、CopyOnWriteArrayList - 线程池 :
ThreadPoolExecutor、Executors
3.3 CAS的正式登场:原子变量类的革命
JDK 1.5最具有前瞻性的设计,是引入了java.util.concurrent.atomic包,提供了AtomicInteger、AtomicLong、AtomicReference等一系列原子类。这些类的底层实现,正是CAS(Compare-And-Swap,比较并交换)------一种源自硬件层面的乐观并发控制技术。
3.3.1 硬件级的乐观锁
CAS是一种乐观锁 策略。它包含三个操作数:内存位置(V)、期望值(A)、新值(B) 。只有当V的值等于A时,才将V更新为B,且整个操作是原子的。
CAS依赖于现代CPU提供的特定指令(如x86架构的cmpxchg),由硬件保证其原子性,避免了用户态锁的介入。它的核心思想是:假设没有冲突,能改就改;如果改的时候发现被动了,那就重试。
3.3.2 无锁编程的实践
以AtomicInteger为例,其incrementAndGet()方法的内部实现是一个典型的CAS循环:
java
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
这种"循环重试 "的模式,就是无锁编程的雏形。虽然它会占用CPU循环,但在低冲突场景下,远比挂起线程高效。这正是乐观锁思想的精髓:用CPU周期换取线程的存活,用自旋替代阻塞。
3.3.3 ReentrantLock的CAS内核
值得注意的是,即便是ReentrantLock这样的显式锁,其底层实现也大量依赖CAS。AbstractQueuedSynchronizer(AQS)作为JUC锁的基石,其核心状态(state)就是一个被CAS操作的volatile变量。获取锁、释放锁的成败,都取决于能否通过CAS成功修改这个状态。可以说,CAS是JUC框架的发动机。
3.4 Unsafe:CAS的底层支撑
JDK 1.5中CAS的广泛应用,离不开一个核心底层工具类------sun.misc.Unsafe。该类提供了直接操作内存、执行原子操作的底层能力,是Java并发工具(如原子类、AQS)实现的核心依赖,也是CAS操作能够落地的关键。
Unsafe的设计初衷是为JDK内部类(如java.util.concurrent.atomic、java.util.concurrent.locks)提供底层支持,并非面向普通开发者。它的核心能力包括:
-
原子操作 :提供
compareAndSwapInt、compareAndSwapLong、compareAndSwapObject等方法,直接封装CPU的CAS指令,是AtomicInteger等原子类的底层实现。例如,AtomicInteger的compareAndSet方法,本质就是调用Unsafe的compareAndSwapInt方法。 -
内存操作:可直接分配、释放内存,操作对象的字段(无需通过反射),突破了Java的安全限制,能直接操作堆外内存,为高性能并发提供了可能。
-
线程调度 :提供
park和unpark方法,用于线程的挂起和唤醒,是AQS实现线程等待队列的核心底层方法。
需要注意的是,Unsafe具有极高的危险性:它绕过了Java的安全检查,直接操作内存和底层资源,一旦使用不当,极易引发内存泄漏、数据错乱甚至JVM崩溃。
因此,JDK官方一直未将其公开暴露,普通开发者无法直接通过正常方式获取其实例(需通过反射),且该类在后续JDK版本中逐渐被更安全的API替代。
3.5 JDK 1.5的历史定位
JDK 1.5标志着Java并发编程从"单一路径"走向"多元化":
| 并发模型 | 代表技术 | 适用场景 |
|---|---|---|
| 悲观阻塞 | synchronized |
简单同步,低并发 |
| 显式锁 | ReentrantLock |
需要超时、中断等高级功能 |
| 无锁并发 | Atomic*类 |
简单计数器,状态标志 |
| 并发容器 | ConcurrentHashMap |
高并发数据结构 |
开发者第一次拥有了选择权:可以根据场景在互斥阻塞和乐观自旋之间做出权衡。
第四章:双雄并立------JDK 1.6的锁优化与性能之争(2006年)

就在开发者们逐渐转向ReentrantLock和原子类时,JVM团队没有放弃synchronized。
从JDK 1.6开始,HotSpot VM对synchronized进行了大刀阔斧的优化,引入了锁升级 机制,使其性能在某些场景下甚至超越了ReentrantLock。
4.1 对象头与Mark Word
synchronized的优化依赖于对象在内存中的布局。每个Java对象的对象头中都有一块称为Mark Word 的区域,它记录了对象的哈希码、GC年龄以及锁状态标志。锁升级的过程,就是Mark Word中标志位变化的过程。
4.2 锁升级路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
-
偏向锁(Biased Locking):针对锁大多由同一线程获得的场景。当线程第一次进入同步块,JVM会将Mark Word中的线程ID设为当前线程。此后该线程再次进入时,只需比对ID,几乎零开销。如果其他线程来竞争,偏向锁即撤销。
-
轻量级锁(Lightweight Locking) :当竞争出现时,锁升级为轻量级锁。线程会在自己的栈帧中创建锁记录(Lock Record),通过CAS自旋尝试修改Mark Word指向自己的锁记录。注意,这里的CAS正是JDK 1.5引入的原子操作在JVM内部的运用------JVM自身也开始用CAS优化锁的获取。
-
重量级锁(Heavyweight Locking) :当自旋超过一定阈值,或等待线程数过多,锁会膨胀为重量级锁。此时Mark Word指向一个操作系统级别的互斥量(mutex)。未获取锁的线程进入阻塞队列,由操作系统调度,涉及用户态与内核态的切换,开销最大。
4.3 CAS的双重角色
从这个阶段开始,CAS在Java并发体系中扮演着双重角色:
- 应用层面 :开发者通过
AtomicInteger等类直接使用CAS,实现无锁并发。 - JVM层面 :虚拟机内部用CAS优化
synchronized的轻量级锁获取,用CAS实现偏向锁的撤销。
4.4 两种路径的对比
到了JDK 1.6,Java实际上形成了两条并行的并发技术路线:
| 技术路线 | 代表 | 核心机制 | 优势 |
|---|---|---|---|
| 内置锁路线 | synchronized |
锁升级 + JVM级CAS | 简单易用,自动优化 |
| 显式锁路线 | ReentrantLock + Atomic* |
应用级CAS + 自旋 | 灵活可控,功能丰富 |
第五章:巅峰对决------JDK 1.8的革命性重构(2014年)

JDK 1.8的发布,标志着Java并发技术走向成熟。这一年,ConcurrentHashMap经历了革命性重构,将CAS和synchronized的配合发挥到了极致。
5.1 ConcurrentHashMap的两代变迁
JDK 7时代:分段锁(Segment)
在JDK 7及之前,ConcurrentHashMap采用了分段锁设计。它将整个哈希表分成多个Segment(段),每个Segment独立持有一把锁。当多个线程操作不同Segment中的数据时,可以真正并行执行,大大提高了并发度。
但是,分段锁也存在固有缺陷:内存开销大(需要维护多个锁);段数量固定,扩容困难;且某些操作(如size())需要依次锁住所有段,性能较差。
JDK 8的革新:synchronized + CAS
JDK 8完全摒弃了分段锁,采用了更精细的设计:
- 数据结构 :采用Node数组 + 链表/红黑树。
- 并发控制:写入数据时,根据桶位(bucket)的状态采取不同策略:
- 如果桶位为空 ,则通过CAS直接插入节点------这是无锁并发的实践。
- 如果桶位非空 ,则对链表头节点 或树根节点 使用synchronized加锁------这是细粒度锁的应用。
这种设计实现了"锁粒度细化到单个桶 ",且巧妙利用了synchronized在JDK 8中已优化的性能。写线程只需锁住自己正在操作的桶,其他桶的访问完全不受影响。
这是"CAS+局部锁"的完美结合,也是两条并发技术路线从竞争走向融合的标志。
5.2 LongAdder的登场
JDK 8还引入了LongAdder,这是对AtomicLong的进一步优化。在高并发场景下,大量线程同时CAS竞争同一个变量会导致大量重试和缓存抖动。
LongAdder的核心思想是分而治之 :它将一个计数变量拆分成多个单元(Cell),每个线程只更新自己对应的单元,最后求和时再汇总。这实际上是将CAS的竞争从"单点"分散到"多点",进一步提升了吞吐量。
5.3 JDK 8的历史地位
JDK 8标志着Java并发技术走向成熟:
- 锁不再是唯一选择:大量场景可以用无锁数据结构替代锁。
- 悲观与乐观的融合 :
ConcurrentHashMap证明了CAS和synchronized可以协同工作。 - 细粒度成为主流:无论是锁粒度还是数据分片粒度,都走向精细化。
第六章:未来已来------JDK 9到21的持续演进(2017-2023年)

从JDK 9开始,Java进入模块化和快速迭代的时代。并发领域虽然没有革命性变革,但持续进行着优化和调整。
6.1 偏向锁的谢幕
JDK 15 中,偏向锁被标记为废弃;JDK 21 中,偏向锁默认被禁用。原因在于:在高并发应用中,锁通常由不同线程竞争,偏向锁的撤销成本反而成为负担。JVM团队根据实际场景数据,做出了务实的选择------这体现了并发技术演进中"实践检验真理"的原则。
6.2 VarHandle:标准化的内存访问工具
JDK 9引入了java.lang.invoke.VarHandle,作为Unsafe的标准化替代方案,解决了Unsafe安全性差、API不规范的问题,同时保留了底层内存操作和原子操作的能力,成为Java并发底层编程的新选择。
VarHandle的核心优势的在于"安全、标准化、高效",其核心能力与Unsafe对应,但更具规范性:
-
原子操作支持 :提供
compareAndSet、getAndAdd、getAndSet等原子方法,与Unsafe的原子操作功能一致,且支持更丰富的类型(包括基本类型和引用类型),底层同样依赖CPU的CAS指令,性能与Unsafe相当。 -
内存语义控制 :支持通过
memoryOrder参数指定内存访问顺序(如volatile、acquire、release等),灵活控制内存可见性和有序性,比volatile关键字更精细,也比Unsafe的操作更规范。 -
安全性提升 :VarHandle是公开的标准化API,无需通过反射获取,且操作被严格限制在合法范围内,避免了
Unsafe直接操作内存可能引发的风险,同时提供了类型安全检查,减少了编程错误。 -
替代Unsafe的核心场景 :在JDK后续版本中,大量原依赖
Unsafe的类(如原子类、并发容器)逐渐迁移到VarHandle实现,例如AtomicInteger的部分方法在JDK 9+中已通过VarHandle实现,进一步提升了代码的安全性和可维护性。
VarHandle的出现,标志着Java底层并发工具的规范化演进------不再依赖非公开的危险API,而是通过标准化接口提供底层能力,兼顾了高性能和安全性,为后续并发技术的优化奠定了基础。
结语
纵观这二十多年的发展,Java并发安全的演进,从最初粗暴的全局锁,到精细化的读写锁,再到基于CAS的无锁并发,每一次技术跃迁,都是为了在保证线程安全的前提下,将硬件的并行效能发挥到极致。