synchronized (lock) {} 如何实现锁住的

发布于:2025-05-28 ⋅ 阅读:(14) ⋅ 点赞:(0)

synchronized (lock) 的锁机制是在 JVM 底层实现 的,涉及对象头、监视器锁等核心概念。

让我详细解释:

1. Java对象的内存结构

每个Java对象在内存中的结构包括

|---------------|
|   对象头      |  ← 关键!包含锁信息
|---------------|
|   实例数据    |
|---------------|  
|   对齐填充    |
|---------------|

2. 对象头中的锁信息

// 对象头包含两部分:
// 1. Mark Word(标记字段)- 存储锁状态、hashCode、GC分代年龄等
// 2. Class Pointer(类型指针)- 指向类元数据

// Mark Word 在32位JVM中的布局:
// |------|------|------|------|------|------|------|------|
// |  锁状态标志位 | 偏向锁标志 |    其他信息(hashCode/age等)    |
// |------|------|------|------|------|------|------|------|

4. 字节码层面 

// 原始代码
Object lock = new Object();
synchronized (lock) {
    System.out.println("在锁中");
}
// 编译后的字节码
 0: new           #2     // 创建 Object
 3: dup
 4: invokespecial #1     // 调用构造方法
 7: astore_1              // 存储到局部变量
 8: aload_1               // 加载 lock 对象
 9: dup
10: astore_2              // 存储锁对象引用
11: monitorenter          // ← 进入监视器(获取锁)
12: getstatic     #3      // System.out
15: ldc           #4      // "在锁中"
17: invokevirtual #5      // println
20: aload_2               // 加载锁对象
21: monitorexit           // ← 退出监视器(释放锁)
22: goto          30
25: aload_2
26: monitorexit           // ← 异常情况下也要释放锁
27: athrow
28: return

5. Monitor(监视器)机制

// 每个对象都有一个隐含的监视器锁
public class ObjectMonitor {
    private Thread owner;        // 当前持有锁的线程
    private int recursions;      // 重入次数
    private Queue<Thread> entryQueue;  // 等待获取锁的线程队列
    private Queue<Thread> waitQueue;   // 调用wait()的线程队列
    
    // 获取锁的过程
    public void enter(Thread thread) {
        if (owner == null) {
            // 锁空闲,直接获取
            owner = thread;
            recursions = 1;
        } else if (owner == thread) {
            // 重入锁
            recursions++;
        } else {
            // 锁被其他线程持有,进入等待队列
            entryQueue.add(thread);
            thread.park(); // 阻塞当前线程
        }
    }
    
    // 释放锁的过程
    public void exit(Thread thread) {
        if (owner == thread) {
            recursions--;
            if (recursions == 0) {
                owner = null;
                // 唤醒等待队列中的线程
                Thread nextThread = entryQueue.poll();
                if (nextThread != null) {
                    nextThread.unpark();
                }
            }
        }
    }
}

6. 锁的状态转换(锁升级)

// JVM 对 synchronized 进行了优化,锁有多种状态:

// 1. 无锁状态
Object obj = new Object();

// 2. 偏向锁(只有一个线程访问)
synchronized (obj) {
    // 第一次获取锁,设置为偏向锁
    // Mark Word 记录线程ID
}

// 3. 轻量级锁(少量线程竞争)
// 当有其他线程尝试获取偏向锁时,升级为轻量级锁
// 使用 CAS(Compare-And-Swap)操作

// 4. 重量级锁(激烈竞争)
// 当轻量级锁竞争激烈时,升级为重量级锁
// 使用操作系统的互斥量(mutex)

7. 实际的锁住过程 

public class SynchronizedDemo {
    public static void main(String[] args) {
        Object lock = new Object();
        
        // 线程A
        new Thread(() -> {
            synchronized (lock) { // monitorenter
                System.out.println("线程A获取锁");
                
                // JVM 内部操作:
                // 1. 检查 lock 对象的 Mark Word
                // 2. 如果无锁,设置当前线程为 owner
                // 3. 如果有锁且不是当前线程,进入等待队列并阻塞
                
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程A释放锁");
            } // monitorexit
        }).start();
        
        // 线程B
        new Thread(() -> {
            synchronized (lock) { // monitorenter - 会阻塞在这里
                System.out.println("线程B获取锁");
            } // monitorexit
        }).start();
    }
}

8. 关键点总结

  1. 对象头存储锁信息:每个对象都有监视器锁
  2. monitorenter/monitorexit 指令:字节码层面的锁获取/释放
  3. Monitor 对象:JVM 内部管理锁状态和等待队列
  4. 线程阻塞机制:通过 park/unpark 实现线程的阻塞和唤醒
  5. 锁优化:偏向锁 → 轻量级锁 → 重量级锁的升级过程

所以当写 synchronized (lock) 时,JVM 会:

  • 检查 lock 对象的锁状态
  • 如果无锁,将当前线程设为持有者
  • 如果有锁,将当前线程放入等待队列并阻塞
  • 当锁释放时,唤醒等待队列中的下一个线程

这就是 synchronized 能够实现互斥访问的底层原理!


网站公告

今日签到

点亮在社区的每一天
去签到