🔒 synchronized 实现原理:图书馆的魔法单间

65 阅读5分钟

🌌 故事背景:带魔法保护的图书馆单间

想象一个图书馆,里面有几个特殊的 "魔法单间":

  • 单间规则:同一时间只能有一人进入

  • 魔法标记:当有人进入时,单间外会自动挂起 "使用中" 的牌子

  • 自动释放:使用者离开时,牌子会自动消失

  • 排队机制:如果单间已满,其他人需要在门口排队等待

这个魔法单间就是synchronized,它是 Java 中实现同步的基础机制。

🚪 魔法单间的核心规则

1. 对象监视器(Monitor)

每个 Java 对象都有一个内置的 "监视器",就像魔法单间的 "使用中" 牌子:

  • 当线程进入synchronized块时,它会尝试获取对象的监视器
  • 如果监视器已被其他线程持有,则当前线程被阻塞
  • 当线程退出synchronized块时,它会释放监视器

2. 同步块与方法

java

public class Library {
    private final Object lock = new Object();  // 魔法单间的锁
    
    // 同步方法(等同于synchronized(this))
    public synchronized void readBook() {
        System.out.println(Thread.currentThread().getName() + " 进入单间阅读");
        try {
            Thread.sleep(1000);  // 模拟阅读时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 离开单间");
    }
    
    // 同步代码块
    public void writeBook() {
        synchronized (lock) {  // 使用特定对象作为锁
            System.out.println(Thread.currentThread().getName() + " 进入单间写作");
            try {
                Thread.sleep(1500);  // 模拟写作时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 离开单间");
        }
    }
}

🧙 魔法背后的字节码

1. 同步方法的字节码

java

public synchronized void readBook();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED  // 方法标志包含ACC_SYNCHRONIZED
    Code:
        stack=2, locals=1, args_size=1
           0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
           3: new           #3                  // class java/lang/StringBuilder
           6: dup
           7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
          10: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
          13: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
          16: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          19: ldc           #8                  // String 进入单间阅读
          21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          24: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          27: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          ...

2. 同步代码块的字节码

java

public void writeBook();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
        stack=2, locals=3, args_size=1
           0: aload_0
           1: getfield      #11                 // Field lock:Ljava/lang/Object;
           4: dup
           5: astore_1
           6: monitorenter   // 进入同步块,获取监视器
           7: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
          10: new           #3                  // class java/lang/StringBuilder
          13: dup
          14: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
          17: invokestatic  #5                  // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
          20: invokevirtual #6                  // Method java/lang/Thread.getName:()Ljava/lang/String;
          23: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          26: ldc           #12                 // String 进入单间写作
          28: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
          31: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
          34: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
          ...
          55: aload_1
          56: monitorexit    // 正常退出同步块,释放监视器
          57: goto          65
          60: astore_2
          61: aload_1
          62: monitorexit    // 异常退出同步块,释放监视器
          63: aload_2
          64: athrow
          65: return

🧠 JVM 中的锁实现

1. 对象头(Mark Word)

每个 Java 对象在内存中都有一个对象头,其中包含锁状态信息:

  • 无锁状态
  • 偏向锁状态
  • 轻量级锁状态
  • 重量级锁状态

2. 锁升级过程

java

// 简化的锁升级流程图
无锁状态 → 偏向锁 → 轻量级锁 → 重量级锁

偏向锁(Biased Locking)

  • 适用于单线程环境
  • 第一次获取锁时,将线程 ID 记录在对象头中
  • 后续同一线程再次获取锁时,无需任何同步操作

轻量级锁(Lightweight Locking)

  • 适用于多线程交替执行同步块的场景
  • 通过 CAS 操作尝试获取锁
  • 如果锁被其他线程持有,则升级为重量级锁

重量级锁(Heavyweight Locking)

  • 适用于多线程竞争激烈的场景
  • 使用操作系统的互斥量(Mutex)实现
  • 线程获取锁失败会被挂起,性能开销较大

🚀 完整示例:图书馆单间使用

java

public class LibraryDemo {
    private final Object readingRoom = new Object();  // 阅读单间
    private final Object writingRoom = new Object();  // 写作单间
    
    public void useReadingRoom() {
        synchronized (readingRoom) {
            System.out.println(Thread.currentThread().getName() + " 进入阅读单间");
            try {
                Thread.sleep(1000);  // 模拟阅读时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 离开阅读单间");
        }
    }
    
    public void useWritingRoom() {
        synchronized (writingRoom) {
            System.out.println(Thread.currentThread().getName() + " 进入写作单间");
            try {
                Thread.sleep(1500);  // 模拟写作时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 离开写作单间");
        }
    }
    
    public static void main(String[] args) {
        LibraryDemo library = new LibraryDemo();
        
        // 启动多个读者线程
        for (int i = 1; i <= 3; i++) {
            final int readerId = i;
            new Thread(() -> {
                library.useReadingRoom();
                library.useWritingRoom();
            }, "读者" + readerId).start();
        }
    }
}

运行结果示例

plaintext

读者1 进入阅读单间
读者1 离开阅读单间
读者1 进入写作单间
读者2 进入阅读单间
读者2 离开阅读单间
读者2 等待写作单间...
读者1 离开写作单间
读者2 进入写作单间
读者3 进入阅读单间
...

🌟 synchronized 核心特性总结

  1. 内置锁机制

    • 每个 Java 对象都可以作为锁
    • 由 JVM 自动管理锁的获取和释放
  2. 锁升级优化

    • 偏向锁:单线程环境下零成本
    • 轻量级锁:多线程交替执行时高效
    • 重量级锁:竞争激烈时保证正确性
  3. 可重入性

    • 同一线程可以多次获取同一把锁
    • 通过锁计数器实现
  4. 线程安全

    • 保证同一时间只有一个线程执行同步代码

    • 解决原子性、可见性和有序性问题

通过这个图书馆的故事,我们理解了synchronized的核心原理:它就像图书馆的魔法单间,通过对象监视器和锁升级机制,在保证线程安全的同时,尽可能地提高性能。在实际开发中,synchronized是最简单、最常用的同步手段,适合大多数场景,而无需手动管理锁的获取和释放。