🔮 synchronized 实现原理:魔法城堡的守护结界

155 阅读7分钟

将用一个"魔法城堡"的故事,带你深入理解 synchronized 的底层实现原理。这座城堡有一道神奇结界,保护着珍贵的宝物(共享资源),而这道结界的运作机制正是 synchronized 的完美隐喻!

🏰 故事背景:魔法城堡的结界系统

想象一座神奇的魔法城堡:

  • 宝物房间 = 共享资源
  • 魔法师 = 线程(Thread)
  • 结界 = synchronized 关键字
  • 结界之书 = 对象头(Object Header)
  • 守护精灵 = 监视器(Monitor)

每个宝物房间(对象)都有一本结界之书(对象头),记录着结界的当前状态。魔法师(线程)想进入房间时,必须通过结界系统的验证!


📜 第一章:结界之书(对象头结构)

每个Java对象都有隐藏的结界之书(对象头),包含关键信息:

java

// 64位JVM对象头结构 (简化表示)
|-------------------------------------------------------|
|  Mark Word (64 bits)                                  | 
|-------------------------------------------------------|
|  unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 
|-------------------------------------------------------|
|  ptr_to_monitor:62                                   | lock:2
|-------------------------------------------------------|
|  ptr_to_heavyweight_monitor:62                       | lock:2
|-------------------------------------------------------|

🧙 结界状态解读:

  1. 无锁状态(001):

    • 存储对象的hashCode和分代年龄
    • 任何魔法师都可尝试进入
  2. 偏向锁(101):

    • 存储持有线程ID和epoch
    • 城堡记住常客,快速通行
  3. 轻量级锁(00):

    • 存储指向栈中锁记录的指针
    • 魔法师们友好竞争
  4. 重量级锁(10):

    • 存储指向监视器(Monitor)的指针
    • 需要守护精灵管理队列

🔄 第二章:结界升级之路(锁膨胀过程)

场景1:无竞争 → 偏向锁(Biased Locking)

java

public class TreasureRoom {
    private int gold = 1000;
    
    public synchronized void addGold(int amount) {
        gold += amount; // 只有一个魔法师常来
    }
}

结界运作

  1. 第一个魔法师A进入房间
  2. 结界记录魔法师ID到结界之书(偏向锁)
  3. 魔法师A后续进入只需验证ID,直接通行(无CAS)

⏳ 偏向锁撤销:

当魔法师B尝试进入:

  1. 检查结界之书,发现已被A占据
  2. 暂停魔法世界(STW安全点)
  3. 若A仍在房间 → 升级为轻量级锁
  4. 若A已离开 → 重置为无锁状态

场景2:轻度竞争 → 轻量级锁(Lightweight Locking)

java

public void transfer(TreasureRoom from, TreasureRoom to, int amount) {
    synchronized(from) {
        synchronized(to) { // 两个魔法师交替访问
            from.gold -= amount;
            to.gold += amount;
        }
    }
}

结界运作

  1. 魔法师A进入前,在栈上创建锁记录(Lock Record)

  2. 复制结界之书内容到锁记录(Displaced Mark Word)

  3. 用CAS尝试将对象头指向锁记录:

    java

    if (CAS(objectMarkWord, 
             expected: unbiasedPattern, 
             newValue: pointerToLockRecord)) {
        // 获取轻量级锁成功
    } else {
        // 升级为重量级锁
    }
    
  4. 成功:进入房间(锁标志位00)

  5. 失败(竞争):膨胀为重量级锁


场景3:激烈竞争 → 重量级锁(Heavyweight Locking)

java

public class Castle {
    public static void main(String[] args) {
        TreasureRoom room = new TreasureRoom();
        // 100个魔法师争夺宝物
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                synchronized(room) {
                    room.addGold(10);
                }
            }).start();
        }
    }
}

结界运作

  1. 轻量级锁竞争失败
  2. 召唤守护精灵(ObjectMonitor)
  3. 结界之书记录监视器地址(锁标志位10)
  4. 未获锁的魔法师进入等待队列

👁️ 第三章:守护精灵的真身(ObjectMonitor)

每个对象在锁膨胀时,都会关联一个ObjectMonitor(C++实现):

cpp

// hotspot/src/share/vm/runtime/objectMonitor.hpp
class ObjectMonitor {
    volatile markOop   _header;      // 原始对象头备份
    void*     volatile _object;      // 关联的对象
    volatile intptr_t  _count;       // 重入次数
    volatile intptr_t  _waiters;     // 等待线程数
    volatile intptr_t  _recursions;  // 重入计数
    Thread*  volatile _owner;        // 当前持有线程
    ObjectWaiter* volatile _EntryList; // 竞争队列
    ObjectWaiter* volatile _WaitSet;  // 等待队列(调用wait的线程)
    
    // 关键方法
    void enter(TRAPS);   // 获取锁
    void exit(bool not_suspended, TRAPS); // 释放锁
};

🧚 守护精灵的工作流程:

deepseek_mermaid_20250626_9f43ee.png


🔐 第四章:进入结界的魔法(monitorenter字节码)

当魔法师尝试进入同步块时:

java

public void enterRoom() {
    synchronized(this) { // ← 触发monitorenter
        // 宝物操作
    } // ← 触发monitorexit
}

编译后的字节码:

java

aload_0         // 加载this引用
dup             // 复制引用
astore_1        // 存储到局部变量1
monitorenter    // 进入监视器
try {
    // 同步块代码...
} finally {
    aload_1
    monitorexit  // 退出监视器
}

⚙️ monitorenter 的魔法逻辑:

  1. 检查结界类型:

    • 偏向锁:尝试重偏向或撤销
    • 轻量级锁:尝试栈锁CAS
    • 重量级锁:调用ObjectMonitor::enter
  2. 重量级锁获取流程:

    cpp

    // objectMonitor.cpp
    void ObjectMonitor::enter(TRAPS) {
        if (TryLock(Self) > 0) return; // 快速获取
        if (TrySpin(Self) > 0) return; // 自旋尝试
        
        // 添加到竞争队列
        EnterI(THREAD);
    }
    
    void ObjectMonitor::EnterI(TRAPS) {
        // 创建等待节点
        ObjectWaiter node(Self);
        Self->_ParkEvent->reset();
        node._prev = (ObjectWaiter*)&_EntryList;
        
        // 插入队列尾部
        ObjectWaiter* tail = _EntryList;
        if (tail == NULL) {
            _EntryList = &node;
        } else {
            while (tail->_next != NULL) tail = tail->_next;
            tail->_next = &node;
        }
        
        // 阻塞当前线程
        Self->_ParkEvent->park();
    }
    

🚪 第五章:离开结界的仪式(monitorexit)

当魔法师完成宝物操作:

java

void ObjectMonitor::exit(bool not_suspended, TRAPS) {
    if (_recursions != 0) { // 重入计数减1
        _recursions--;
        return;
    }
    
    // 唤醒等待队列
    if (_EntryList != NULL) {
        ObjectWaiter* w = _EntryList;
        _EntryList = w->_next;
        w->_thread->_ParkEvent->unpark(); // 唤醒线程
    } else if (_cxq != NULL) {
        // 处理cxq队列...
    }
    
    // 重置所有者
    _owner = NULL; 
}

🔄 重入锁的实现:

java

public class RecursiveMagic {
    public synchronized void outer() {
        inner(); // 重入内部同步方法
    }
    
    public synchronized void inner() {
        // 宝物操作
    }
}
  • 每次进入:_recursions++
  • 每次退出:_recursions--
  • 仅当_recursions==0时完全释放

⚡ 第六章:现代结界的优化(JVM黑科技)

1. 锁消除(Lock Elision)

java

public String concat(String s1, String s2) {
    StringBuffer sb = new StringBuffer();
    sb.append(s1); // 同步方法,但JVM检测到线程安全
    sb.append(s2);
    return sb.toString();
}

JVM检测到sb不会逃逸出方法,自动移除同步!

2. 锁粗化(Lock Coarsening)

java

public void addItems(List<Item> items) {
    for (Item item : items) {
        synchronized(this) { // 连续多次加锁解锁
            inventory.add(item);
        }
    }
}

JVM优化为:

java

synchronized(this) {
    for (Item item : items) {
        inventory.add(item);
    }
}

3. 自适应自旋(Adaptive Spinning)

java

// objectMonitor.cpp
int ObjectMonitor::TrySpin(Thread * Self) {
    int ctr = _SpinDuration; // 动态调整的自旋次数
    while (--ctr >= 0) {
        if (TryLock(Self) > 0) return 1;
        SpinPause(); // CPU暂停指令(避免忙等消耗)
    }
    return 0;
}

JVM根据历史成功率动态调整自旋次数


⚔️ 第七章:synchronized vs ReentrantLock

特性synchronizedReentrantLock
实现级别JVM原生(字节码指令)JDK代码(基于AQS)
锁获取自动获取和释放手动lock()/unlock()
灵活性基本功能高级功能(公平锁、条件等)
性能Java 6后大幅优化高竞争下表现更好
锁升级支持(无锁→偏向→轻量→重量)始终是重量级锁
死锁检测不支持提供tryLock()死锁检测
可中断等待中不可中断lockInterruptibly()支持中断
绑定与对象/方法绑定独立锁对象
代码简洁性简洁(语法糖)需try-finally保证释放

🧪 第八章:从源码看结界魔法

1. 对象头解析工具(JOL)

java

// 添加依赖:org.openjdk.jol:jol-core
public class HeaderInspection {
    public static void main(String[] args) {
        Object obj = new Object();
        System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        
        synchronized(obj) {
            System.out.println(ClassLayout.parseInstance(obj).toPrintable());
        }
    }
}

2. 输出示例(64位系统):

text

// 无锁状态
OFFSET  SIZE   TYPE DESCRIPTION
0      4        (object header)  01 00 00 00 
4      4        (object header)  00 00 00 00 
8      4        (object header)  e5 01 00 f8

// 重量级锁状态
OFFSET  SIZE   TYPE DESCRIPTION
0      4        (object header)  5a 3b 03 03 
4      4        (object header)  00 70 00 00 

3. 监视器查看工具(jstack)

bash

$ jstack <pid>
"main" #1 prio=5 os_prio=0 tid=0x00007f... nid=0x7e9c 
   java.lang.Thread.State: BLOCKED (on object monitor)
   at TreasureRoom.addGold(TreasureRoom.java:10)
   - waiting to lock <0x000000076ab62d98> (a TreasureRoom)

💎 总结:synchronized 设计精髓

  1. 渐进式锁升级

    • 偏向锁 → 减少无竞争开销
    • 轻量级锁 → 减少轻度竞争开销
    • 重量级锁 → 保证高竞争稳定性
  2. 对象头集成

    • Mark Word 存储锁状态
    • 无额外内存开销(对比ReentrantLock)
  3. JVM深度优化

    • 锁消除/粗化
    • 自适应自旋
    • 逃逸分析支持
  4. 硬件级协同

    • CAS指令实现轻量级锁
    • 内存屏障保证可见性
    • pause指令优化自旋

🌟 魔法启示
synchronized = 对象头锁状态 + CAS轻量竞争 + Monitor队列管理
如同魔法城堡的智能结界系统,根据访客情况自动调整防护策略,既高效又安全地守护着共享资源!

通过这个故事,你已经深入理解了 synchronized 从字节码到JVM再到操作系统的完整实现路径。下次使用 synchronized 时,想象那座魔法城堡的结界,感受Java并发设计的精妙之处吧! 🏰✨