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. 关键点总结
- 对象头存储锁信息:每个对象都有监视器锁
- monitorenter/monitorexit 指令:字节码层面的锁获取/释放
- Monitor 对象:JVM 内部管理锁状态和等待队列
- 线程阻塞机制:通过 park/unpark 实现线程的阻塞和唤醒
- 锁优化:偏向锁 → 轻量级锁 → 重量级锁的升级过程
所以当写 synchronized (lock)
时,JVM 会:
- 检查 lock 对象的锁状态
- 如果无锁,将当前线程设为持有者
- 如果有锁,将当前线程放入等待队列并阻塞
- 当锁释放时,唤醒等待队列中的下一个线程
这就是 synchronized
能够实现互斥访问的底层原理!