在Java中,锁是实现多线程同步的核心机制。不同的锁适用于不同的场景,理解其实现原理和使用方法对优化性能和避免并发问题至关重要。
一、隐式锁:synchronized
关键字
实现原理
- 基于对象监视器(Monitor):每个Java对象都有一个内置的监视器锁(monitor lock),通过
synchronized
关键字获取。 - 锁升级机制(JVM优化):
- 偏向锁:无竞争时,标记线程ID,避免CAS操作。
- 轻量级锁:通过CAS竞争锁,失败后升级为重量级锁。
- 重量级锁:通过操作系统互斥量(mutex)实现线程阻塞。
使用方法
// 1. 同步代码块
synchronized (obj) { // 临界区代码
}// 2. 同步实例方法
public synchronized void method() { }// 3. 同步静态方法
public static synchronized void method() { }
适用场景
- 简单同步需求:无需复杂锁功能的场景(如可中断、超时等)。
- 代码简洁性优先:自动释放锁,避免忘记解锁的风险。
二、显式锁:ReentrantLock
实现原理
- 基于AQS(AbstractQueuedSynchronizer):
- 通过
state
变量(CAS操作)记录锁状态。 - 使用CLH队列管理等待线程。
- 通过
- 支持公平性:可选择公平锁(按排队顺序获取)或非公平锁(插队竞争)。
使用方法
ReentrantLock lock = new ReentrantLock();
lock.lock();
try {// 临界区代码
} finally {lock.unlock(); // 必须手动释放
}// 高级功能示例:尝试获取锁
if (lock.tryLock(1, TimeUnit.SECONDS)) {try { /* ... */ } finally { lock.unlock(); }
}
适用场景
- 复杂锁需求:需要可中断、超时、公平性等特性。
- 细粒度控制:如跨方法加锁解锁(
synchronized
只能在代码块内)。
三、读写锁:ReentrantReadWriteLock
实现原理
- 分离读锁(共享)和写锁(独占):
- 读锁允许多线程并发读,写锁独占。
- AQS的
state
高16位记录读锁,低16位记录写锁。
使用方法
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();// 读操作
readLock.lock();
try { /* 读数据 */ } finally { readLock.unlock(); }// 写操作
writeLock.lock();
try { /* 写数据 */ } finally { writeLock.unlock(); }
适用场景
- 读多写少:如缓存系统、高频查询场景。
- 数据一致性要求:写操作需要互斥,读操作可并发。
四、乐观锁:StampedLock
(Java 8+)
实现原理
- 基于票据(Stamp)的锁机制:
- 支持三种模式:写锁、悲观读锁、乐观读。
- 乐观读不阻塞写操作,通过验证Stamp判断数据一致性。
使用方法
StampedLock stampedLock = new StampedLock();// 乐观读
long stamp = stampedLock.tryOptimisticRead();
// 读取数据
if (!stampedLock.validate(stamp)) {// 数据被修改,升级为悲观读锁stamp = stampedLock.readLock();try { /* 重新读取数据 */ } finally { stampedLock.unlockRead(stamp); }
}// 写锁
long stamp = stampedLock.writeLock();
try { /* 写数据 */ } finally { stampedLock.unlockWrite(stamp); }
适用场景
- 读多写少且容忍数据不一致:如统计、日志处理。
- 极高性能需求:乐观读避免锁竞争,但需处理验证逻辑。
五、其他锁机制
1. Condition
条件变量
- 与
ReentrantLock
配合使用,实现线程间协作(类似wait/notify
)。 - 典型场景:生产者-消费者模型。
2. 分布式锁
- 如基于Redis的
Redisson
或ZooKeeper实现。 - 适用场景:跨JVM或分布式系统同步。
六、锁的选择与性能优化
锁对比表
锁类型 | 特性 | 性能 | 适用场景 |
---|---|---|---|
synchronized | 自动释放,非公平锁 | 低竞争时高效 | 简单同步需求 |
ReentrantLock | 可中断、超时、公平锁 | 高竞争时高效 | 复杂锁需求 |
ReadWriteLock | 读写分离 | 读多写少高效 | 缓存、查询系统 |
StampedLock | 乐观读,支持锁升级 | 极高并发 | 读多写少,容忍数据不一致 |
最佳实践
- 减少锁粒度:缩小临界区范围。
- 避免嵌套锁:防止死锁(如按固定顺序获取锁)。
- 监控锁竞争:使用JProfiler或JStack分析锁状态。
七、总结
- 简单场景优先选择
synchronized
(JVM优化成熟)。 - 复杂需求使用
ReentrantLock
或ReadWriteLock
。 - 极致性能考虑
StampedLock
,但需谨慎处理数据一致性。
合理选择锁类型,结合性能测试和监控,是构建高效并发系统的关键。