将通过一个"电影院智能检票系统"的故事,带您深入理解synchronized的实现原理。想象一座现代化的电影院,观众(线程)需要有序入场观影,而synchronized就是这座电影院的智能检票系统。
故事设定:电影院检票系统
- 🎦 放映厅:共享资源
- 🎫 电影票:锁对象
- 👨👩👧 观众:线程
- 🤖 智能检票机:synchronized机制
- 📊 电子显示屏:对象头信息
- 🚪 VIP通道/普通通道:锁升级策略
synchronized三种形态的演变
1. 偏向锁(VIP快速通道)
java
Copy
// 代码示例:单线程场景
public class Cinema {
private int seats = 100;
public synchronized void enter() {
seats--;
System.out.println("一个观众入场,剩余座位:" + seats);
}
public static void main(String[] args) {
Cinema cinema = new Cinema();
// 只有一个观众(单线程)
for (int i = 0; i < 10; i++) {
cinema.enter();
}
}
}
对象头结构(64位JVM):
Copy
|-------------------------------------------------------|
| Mark Word (64 bits) |
|-------------------------------------------------------|
| unused:25 | threadId:31 | epoch:1 | age:4 | 1 | 01 |
|-------------------------------------------------------|
偏向锁标志 持有线程ID 时间戳 分代年龄 偏向锁 锁标志
工作流程:
-
首次访问:CAS设置对象头中的线程ID
-
后续访问:检查对象头中的线程ID
- 匹配:直接通行(无需额外操作)
- 不匹配:升级为轻量级锁
✅ 优势:单线程下零成本加锁
2. 轻量级锁(普通检票通道)
java
Copy
// 代码示例:低竞争多线程
public static void main(String[] args) {
Cinema cinema = new Cinema();
// 两个观众交替入场(低竞争)
new Thread(() -> {
for (int i = 0; i < 50; i++) {
cinema.enter();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 50; i++) {
cinema.enter();
}
}).start();
}
锁升级过程:
- 在栈帧中创建锁记录(Lock Record)
- 拷贝对象头Mark Word到锁记录
- CAS尝试将对象头指向锁记录指针
- 成功:获取轻量级锁
- 失败:存在竞争,升级为重量级锁
对象头变化:
Copy
|-------------------------------------------------------|
| Mark Word (64 bits) |
|-------------------------------------------------------|
| ptr_to_lock_record:62 | 00 |
|-------------------------------------------------------|
指向栈中锁记录的指针 轻量级锁标志
3. 重量级锁(高峰期排队系统)
java
Copy
// 代码示例:高竞争场景
public static void main(String[] args) {
Cinema cinema = new Cinema();
// 100个观众同时抢票(高竞争)
for (int i = 0; i < 100; i++) {
new Thread(() -> {
cinema.enter();
}).start();
}
}
重量级锁结构:
c
Copy
// OpenJDK源码:ObjectMonitor结构
class ObjectMonitor {
volatile markOop _header; // 对象头拷贝
void* volatile _object; // 关联的对象
Thread* volatile _owner; // 持有锁的线程
ObjectWaiter* volatile _EntryList; // 等待锁的线程列表
ObjectWaiter* volatile _WaitSet; // 调用wait()的线程集合
volatile int _count; // 重入次数
volatile int _waiters; // 等待线程数
// ... 其他字段
}
工作流程:
-
竞争失败线程进入_EntryList
-
当持有锁线程释放时:
- 从_EntryList唤醒一个线程
- 被唤醒线程尝试获取锁
-
调用wait()的线程进入_WaitSet
-
notify()/notifyAll()唤醒线程到_EntryList
底层实现深度解析
1. 字节码层面
java
Copy
public synchronized void method() {}
编译后:
Copy
public synchronized void method();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
java
Copy
public void method() {
synchronized(obj) {}
}
编译后:
Copy
public void method();
Code:
0: aload_1 // 加载对象引用
1: dup // 复制栈顶值
2: astore_2 // 存储引用到局部变量
3: monitorenter // 进入监视器
4: aload_2
5: monitorexit // 正常退出监视器
6: goto 14
9: astore_3
10: aload_2
11: monitorexit // 异常退出监视器
12: aload_3
13: athrow
14: return
2. monitorenter指令解析
cpp
Copy
// OpenJDK源码:解释器执行monitorenter
void InterpreterRuntime::monitorenter(JavaThread* current, BasicObjectLock* elem) {
Handle h_obj(current, elem->obj());
// 尝试快速获取锁
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
// 尝试偏向锁逻辑
}
}
// 轻量级锁获取
if (LockingMode == LM_LIGHTWEIGHT) {
if (h_obj()->mark().is_marked()) {
// 轻量级锁快速路径
}
}
// 最终降级到重量级锁
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), current);
}
3. 自适应自旋优化
cpp
Copy
// OpenJDK源码:自适应自旋逻辑
int ObjectMonitor::TrySpin(Thread * self) {
int ctr = Knob_SpinAfterFutile;
if (ctr != 0) {
// 基于历史成功率调整自旋次数
ctr = Self->_SpinFreq;
while (--ctr >= 0) {
if (TryLock(self) > 0) return 1;
SpinPause();
}
}
return 0;
}
synchronized的优化策略
1. 锁消除(Lock Elision)
java
Copy
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer();
sb.append(s1);
sb.append(s2);
return sb.toString();
}
JVM检测到StringBuffer不会逃逸出方法 → 消除锁操作
2. 锁粗化(Lock Coarsening)
java
Copy
// 优化前
for (int i = 0; i < 100; i++) {
synchronized(lock) {
// 操作
}
}
// 优化后
synchronized(lock) {
for (int i = 0; i < 100; i++) {
// 操作
}
}
3. 批量重偏向与撤销
当多个线程访问同一个类的不同实例时:
- 批量重偏向:第20个实例后,JVM将锁重偏向到新线程
- 批量撤销:第40个实例后,JVM禁用该类的偏向锁
对象生命周期与锁状态
synchronized关键特性
1. 可重入性
java
Copy
public synchronized void methodA() {
methodB(); // 可重入
}
public synchronized void methodB() {
// 重入计数+1
}
实现原理:ObjectMonitor中的_count计数器记录重入次数
2. 内存语义
- 进入同步块:强制从主内存读取最新值
- 退出同步块:强制刷新工作内存到主内存
3. 等待/通知机制
java
Copy
public synchronized void watchMovie() throws InterruptedException {
while (noSeatsAvailable()) {
wait(); // 进入_WaitSet
}
// 观看电影
}
public synchronized void leaveSeat() {
notifyAll(); // 唤醒_WaitSet中的线程
}
性能对比:synchronized vs ReentrantLock
| 场景 | synchronized | ReentrantLock |
|---|---|---|
| 单线程 | 0.3 ns | 6.5 ns |
| 低竞争多线程 | 10 ns | 31 ns |
| 高竞争 | 980 ns | 120 ns |
| 内存占用 | 16 bytes | 96 bytes |
| 可中断等待 | ❌ | ✅ |
| 公平锁 | ❌ | ✅ |
| 条件变量 | 单一 | 多个 |
最佳实践
1. 同步范围最小化
java
Copy
// 不推荐
public synchronized void process() {
// 大量非同步操作
synchronized(this) {
// 实际需要同步的代码
}
// 更多非同步操作
}
2. 使用私有锁对象
java
Copy
public class Cinema {
private final Object lock = new Object();
public void enter() {
synchronized(lock) {
// 同步代码
}
}
}
3. 避免嵌套同步
java
Copy
// 危险:可能导致死锁
public synchronized void methodA() {
methodB();
}
public synchronized void methodB() {
// ...
}
总结:synchronized设计哲学
synchronized的进化体现了Java并发的设计智慧:
- 无竞争优化:偏向锁实现零成本加锁
- 低竞争适应:轻量级锁减少线程阻塞
- 高竞争保障:重量级锁确保系统稳定性
- 运行时自适应:基于实际竞争动态调整策略
就像现代化电影院的检票系统:
- 平时开启VIP通道(偏向锁)
- 周末开启普通通道(轻量级锁)
- 热门大片开放排队区(重量级锁)
这种分层设计使synchronized在JDK6后成为高效、可靠的同步机制,适用于大多数并发场景。