1. 监视器(Monitor)的本质与Java实现
在Java并发编程中,监视器(Monitor)是最基础的线程同步机制。这个概念最早由Per Brinch Hansen和Tony Hoare在1970年代提出,本质上是一种面向对象的线程互斥解决方案。Java语言从1.0版本开始就内置了监视器支持,主要通过synchronized关键字实现。
监视器的核心由三部分组成:
- 互斥锁(Mutex Lock):保证同一时刻只有一个线程能进入临界区
- 条件变量(Condition Variables):用于线程间的等待/通知机制
- 等待队列(Wait Set):存放等待特定条件的线程
在JVM层面,每个Java对象都关联着一个监视器锁(也称为内部锁或管程锁)。当线程进入synchronized方法或代码块时,会自动获取这个锁;退出时自动释放。这种设计使得Java中的对象天然具备线程同步能力。
关键点:监视器锁是可重入的,同一个线程可以多次获取同一个锁而不会死锁。JVM会维护一个计数器记录重入次数。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. synchronized的底层实现原理
2.1 字节码层面的实现
编译含有synchronized的代码时,编译器会在字节码中插入monitorenter和monitorexit指令。例如:
java复制public void syncMethod() {
synchronized(this) {
// 临界区代码
}
}
对应的字节码大致如下:
code复制aload_0 // 将this引用压入栈顶
dup // 复制栈顶值
astore_1 // 存储引用到局部变量1
monitorenter // 进入监视器(获取锁)
aload_1 // 加载局部变量1
monitorexit // 退出监视器(释放锁)
goto 14
aload_1 // 异常处理路径
monitorexit // 确保锁被释放
athrow // 抛出异常
2.2 JVM中的锁优化
现代JVM实现了多种锁优化策略:
- 偏向锁(Biased Locking):假设锁总是由同一线程获取,避免同步开销
- 轻量级锁(Lightweight Locking):通过CAS操作实现短时间锁竞争
- 自旋锁(Spin Locking):竞争线程短暂循环等待而非直接挂起
- 锁消除(Lock Elimination):JIT编译器移除不可能存在竞争的锁
- 锁粗化(Lock Coarsening):合并相邻的同步块减少锁操作
这些优化使得在低竞争场景下,synchronized的性能接近无锁操作。
2.3 对象头与Mark Word
每个Java对象在内存中的布局包含对象头(Object Header),其中Mark Word存储了锁状态信息:
code复制|-------------------------------------------------------|--------------------|
| Mark Word (32/64 bits) | State |
|-------------------------------------------------------|--------------------|
| hashcode:25 | age:4 | biased_lock:1 | lock:2 (01) | Normal |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 (01) | Biased |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30 | lock:2 (00) | Lightweight Lock |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30 | lock:2 (10) | Heavyweight Lock |
|-------------------------------------------------------|--------------------|
| | lock:2 (11) | Marked for GC |
|-------------------------------------------------------|--------------------|
锁状态通过最后2位标识,JVM根据竞争情况在不同状态间转换。
3. 线程间通信机制详解
3.1 wait/notify机制
Object类提供的wait(), notify(), notifyAll()方法构成了Java最基础的线程间通信机制:
java复制public class WaitNotifyExample {
private final Object lock = new Object();
private boolean condition = false;
public void waitForCondition() throws InterruptedException {
synchronized (lock) {
while (!condition) {
lock.wait(); // 释放锁并等待
}
// 条件满足后继续执行
}
}
public void signalCondition() {
synchronized (lock) {
condition = true;
lock.notifyAll(); // 唤醒所有等待线程
}
}
}
关键注意事项:
- 必须在同步块内调用wait/notify(否则抛出IllegalMonitorStateException)
- 通常使用while循环而非if检查条件(防止虚假唤醒)
- notify随机唤醒一个等待线程,notifyAll唤醒所有(谨慎选择)
3.2 Condition接口
Java 5引入的Lock框架提供了更灵活的Condition接口:
java复制public class ConditionExample {
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private boolean ready = false;
public void await() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await();
}
} finally {
lock.unlock();
}
}
public void signal() {
lock.lock();
try {
ready = true;
condition.signalAll();
} finally {
lock.unlock();
}
}
}
相比内置的wait/notify,Condition的优势在于:
- 一个Lock可以创建多个Condition
- 支持公平/非公平锁选择
- 提供awaitUninterruptibly()等扩展方法
- 更精确的线程唤醒控制
3.3 生产者-消费者模式实现
经典的生产者-消费者问题展示了线程间通信的实际应用:
java复制public class ProducerConsumer {
private final Queue<Integer> queue = new LinkedList<>();
private final int capacity;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public ProducerConsumer(int capacity) {
this.capacity = capacity;
}
public void produce(int value) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
notFull.await();
}
queue.add(value);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public int consume() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
notEmpty.await();
}
int value = queue.remove();
notFull.signal();
return value;
} finally {
lock.unlock();
}
}
}
4. 高级同步模式与最佳实践
4.1 双重检查锁定模式
单例模式中常见的双重检查锁定需要特别注意:
java复制public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 注意volatile!
}
}
}
return instance;
}
}
关键点:
- volatile防止指令重排序(避免返回未初始化的对象)
- 两次null检查减少同步开销
- 静态内部类实现通常是更好的选择
4.2 读写锁应用
ReentrantReadWriteLock适用于读多写少场景:
java复制public class CachedData {
private Object data;
private volatile boolean cacheValid;
private final ReadWriteLock rwl = new ReentrantReadWriteLock();
public void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// 必须在释放读锁前获取写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 再次检查状态,因为可能有其他线程已经获取写锁并改变了状态
if (!cacheValid) {
data = fetchDataFromDatabase();
cacheValid = true;
}
// 降级为读锁
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // 释放写锁,保持读锁
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
4.3 避免死锁的策略
- 固定锁获取顺序(如按对象hash排序)
- 使用tryLock()设置超时
- 通过jstack或JConsole检测死锁
- 尽量减少同步块大小和持有时间
- 考虑使用并发集合替代显式同步
5. 性能考量与监控
5.1 锁竞争监控
使用JVM参数监控锁情况:
- -XX:+PrintSafepointStatistics
- -XX:+PrintGCDetails -XX:+PrintGCApplicationStoppedTime
- -XX:+PrintConcurrentLocks
工具支持:
- JConsole/JVisualVM的线程监控
- Java Mission Control的锁分析
- async-profiler等性能分析工具
5.2 基准测试对比
不同同步方式的吞吐量对比(ops/ms):
| 同步方式 | 低竞争 | 中等竞争 | 高竞争 |
|---|---|---|---|
| synchronized | 850 | 420 | 120 |
| ReentrantLock | 780 | 450 | 130 |
| ReadWriteLock | 920 | 600 | 150 |
| StampedLock乐观读 | 1050 | 800 | 200 |
| 无锁CAS | 1200 | 950 | 300 |
5.3 最佳实践总结
- 优先考虑无锁数据结构(如ConcurrentHashMap)
- 减小同步块范围(但不要过度分解)
- 考虑读写分离(ReadWriteLock/StampedLock)
- 避免在同步块内调用外部方法(容易引发死锁)
- 使用ThreadLocal存储线程私有数据
- 对于短时操作,自旋可能比阻塞更高效
