synchonized 的底层原理是 Java 对象头 和 Monitor(监视器) 共同作用的结果。它的实现过程会随着锁的竞争情况而升级,这个过程就是著名的 锁升级。
核心概览
synchonized 关键字在 JVM 中的实现原理,可以概括为以下几个关键点:
- 基本单位:它锁住的是对象,而不是代码
- 普通同步方法,锁住当前实例对象。
- 静态同步方法,锁住当前类的Class对象。
- 同步代码块,锁住synchronized括号里配置的对象。
- 实现基础:依赖于 Java 对象头 中的 Mark Word 来存储锁状态信息。
- 核心机制:通过与一个与对象关联的 Monitor 进行交互来实现线程的互斥。
- 优化策略:为了在性能和开销之间取得平衡,JVM 引入了 锁升级 机制,使得
synchronized的性能在现代 JDK 中已经大幅提升。
1. Java 对象头
在 JVM 中,每个 Java 对象在内存中分为三部分:对象头、实例数据和对齐填充字节。其中,对象头是理解锁的关键。对象头主要包含两类信息:
- Mark Word:用于存储对象自身的运行时数据,如哈希码、GC 分代年龄、锁标志等。这是实现锁的核心部分。
- Klass Pointer:指向对象元数据的指针,JVM 通过它来确定这个对象是哪个类的实例。
为了用尽可能少的空间存储更多的信息,Mark Word 被设计成一个非固定的动态数据结构。它在不同锁状态下的存储内容是不同的。下图清晰地展示了其结构:
2. Monitor(监视器)
Monitor 是线程私有的数据结构,每一个被锁住的对象都会和一个 Monitor 关联。可以把它理解为一个特殊的房间,这个房间有一些规则:
- Owner:当前持有锁的线程。
- EntryList:等待锁的阻塞队列。多个线程竞争锁时,没有抢到的线程会进入这个队列等待。
- WaitSet:等待队列。那些获得了锁的线程,如果调用了
wait()方法,就会释放锁并进入这个集合,等待被notify()唤醒。
synchronized 的互斥性就是通过 Monitor 实现的:
- 当线程执行到
synchronized修饰的代码块时,JVM 会尝试将对象的 Mark Word 指向一个 Monitor,并将 Owner 设置为当前线程。 - 如果设置成功,该线程就成功获取了锁,可以执行同步代码。
- 此时如果另一个线程也来执行同步代码,它会发现 Owner 已经是别的线程,于是这个新线程会进入 EntryList 阻塞等待。
- 当持有锁的线程执行完同步代码块后,会释放锁(将 Owner 置为空),并唤醒 EntryList 中等待的线程来重新竞争锁。
3. 锁升级:性能优化的关键
早期的 synchronized 是纯粹的“重量级锁”,直接和操作系统底层的互斥量(Mutex Lock)关联,导致线程的挂起和唤醒需要切换到内核态,开销非常大。
为了优化性能,JDK 1.6 引入了锁升级机制,其路径是:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁。这个升级过程是单向的,目的是减少获得锁和释放锁带来的性能消耗。
第 1 级:偏向锁
- 场景:假设在大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。
- 原理:当一个线程第一次获得锁时,JVM 会将锁标志位设为“偏向模式”(
01),并将线程ID记录到 Mark Word 中。以后这个线程再次进入同步代码块时,只需要检查 Mark Word 中的线程ID是否是自己。如果是,则无需任何同步操作(如CAS),直接进入,开销极小。 - 目的:消除数据在无竞争情况下的同步开销,提高单线程执行同步代码块的性能。
- 升级:一旦有另一个线程来尝试获取锁,偏向锁模式就会宣告结束,开始升级为轻量级锁。
第 2 级:轻量级锁
- 场景:当锁是偏向锁,但有另一个线程来竞争时(轻度竞争)。
- 原理:
- 加锁:JVM 会在当前线程的栈帧中创建一个名为“锁记录”的空间,然后将对象的 Mark Word 复制到其中。然后,JVM 使用 CAS 操作 尝试将对象的 Mark Word 更新为指向该锁记录的指针。
- 如果成功,当前线程获得锁,锁标志位变为
00(轻量级锁状态)。 - 如果失败(说明有其他线程也在竞争),当前线程会通过 自旋 的方式不断尝试获取锁(空循环,不立即放弃CPU)。
- 目的:避免线程在轻度竞争下直接进入重量级锁的阻塞状态,减少用户态到内核态的切换开销。
- 升级:如果自旋了一定次数后(JDK 1.6 后是自适应的)还没有获得锁,或者有第三个线程来竞争,轻量级锁就会升级为重量级锁。
第 3 级:重量级锁
- 场景:线程竞争非常激烈,轻量级锁的自旋消耗了大量CPU资源。
- 原理:此时锁的标志位变为
10,Mark Word 中存储的是指向重量级锁(Monitor)的指针。所有等待锁的线程都会进入 EntryList 并被阻塞,需要操作系统的介入进行线程的调度和唤醒。 - 特点:开销最大,但可以避免CPU的空转。
字节码层面
从字节码角度看,synchronized 同步语句块的实现是基于 monitorenter 和 monitorexit 指令。
- 在代码块开始处插入
monitorenter指令,用于尝试获取锁。 - 在代码块结束处和异常处插入
monitorexit指令,用于释放锁。
JVM 要保证每个 monitorenter 必须有对应的 monitorexit。
总结
| 特性 | 描述 |
|---|---|
| 锁住对象 | 锁是关联在对象上的,通过对象头的 Mark Word 来标识。 |
| Monitor | 是实现互斥的核心机制,负责管理线程的排队和阻塞。 |
| 锁升级 | 核心优化策略,路径为:无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁,根据竞争激烈程度动态调整,兼顾了无竞争和高竞争场景下的性能。 |
| 现代性能 | 在低竞争场景下,synchronized 的性能与 ReentrantLock 相差无几,因其是 JVM 内置特性,推荐优先使用。 |
2725

被折叠的 条评论
为什么被折叠?



