synchronized底层揭秘:电影院智能检票系统

59 阅读5分钟

将通过一个"电影院智能检票系统"的故事,带您深入理解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    时间戳    分代年龄  偏向锁 锁标志

​工作流程​​:

  1. 首次访问:CAS设置对象头中的线程ID

  2. 后续访问:检查对象头中的线程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();
}

​锁升级过程​​:

  1. 在栈帧中创建锁记录(Lock Record)
  2. 拷贝对象头Mark Word到锁记录
  3. CAS尝试将对象头指向锁记录指针
  4. 成功:获取轻量级锁
  5. 失败:存在竞争,升级为重量级锁

​对象头变化​​:

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;          // 等待线程数
    // ... 其他字段
}

​工作流程​​:

  1. 竞争失败线程进入_EntryList

  2. 当持有锁线程释放时:

    • 从_EntryList唤醒一个线程
    • 被唤醒线程尝试获取锁
  3. 调用wait()的线程进入_WaitSet

  4. 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禁用该类的偏向锁

对象生命周期与锁状态

image.png

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 ns6.5 ns
低竞争多线程10 ns31 ns
高竞争980 ns120 ns
内存占用16 bytes96 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并发的设计智慧:

  1. ​无竞争优化​​:偏向锁实现零成本加锁
  2. ​低竞争适应​​:轻量级锁减少线程阻塞
  3. ​高竞争保障​​:重量级锁确保系统稳定性
  4. ​运行时自适应​​:基于实际竞争动态调整策略

就像现代化电影院的检票系统:

  • 平时开启VIP通道(偏向锁)
  • 周末开启普通通道(轻量级锁)
  • 热门大片开放排队区(重量级锁)

这种分层设计使synchronized在JDK6后成为高效、可靠的同步机制,适用于大多数并发场景。