Java 锁相关详解【四、Java 显式锁的高级扩展:ReadWriteLock、StampedLock 与 LockSupport】
本文深入解析了Java高级锁机制:ReadWriteLock实现读写分离,适用于读多写少场景;StampedLock引入乐观读,在极端读多写少情况下性能更优;LockSupport提供底层线程阻塞支持。三者构成Java锁体系的重要补充,与synchronized和ReentrantLock共同形成完整的并发编程工具链。文章比较了各锁特性,建议根据业务场景合理选择:常规读多写少用ReentrantR
Java 显式锁的高级扩展:ReadWriteLock、StampedLock 与 LockSupport
在前两篇文章中,我们已经深入解析了 Java 内置锁 synchronized
与显式锁 ReentrantLock
。
然而在高并发场景下,某些复杂需求仅靠这两种锁还不足以满足:
- 如何在 读多写少 的业务场景下提高并发度?
- 如何在 乐观并发 下减少锁竞争?
- 如何实现 底层线程阻塞/唤醒 的基础设施?
为了解决这些问题,JUC 提供了 ReadWriteLock、StampedLock、LockSupport 等高级锁机制。本文将对它们进行系统性解析。
一、ReadWriteLock:读写分离的锁机制
1.1 基本概念
ReadWriteLock
是一种 读写分离锁,核心思想是:
- 多个读线程可以并发访问(读共享);
- 写线程独占锁,写时禁止任何读写操作;
- 读写互斥、写写互斥、读读不互斥。
1.2 实现类:ReentrantReadWriteLock
JUC 提供的主要实现是 ReentrantReadWriteLock:
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
1.3 使用示例
class SafeCache {
private final Map<String, String> cache = new HashMap<>();
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
public String get(String key) {
readLock.lock();
try {
return cache.get(key);
} finally {
readLock.unlock();
}
}
public void put(String key, String value) {
writeLock.lock();
try {
cache.put(key, value);
} finally {
writeLock.unlock();
}
}
}
1.4 特性
- 可重入性:与 ReentrantLock 一样支持可重入。
- 公平性:支持公平/非公平模式。
- 降级:支持从写锁降级为读锁(但不支持读锁升级为写锁)。
1.5 适用场景
- 读多写少,如缓存系统、本地配置读取。
- 不适合写操作频繁的场景,否则读线程会频繁阻塞。
二、StampedLock:乐观读的锁机制
2.1 背景
虽然 ReentrantReadWriteLock
能解决读多写少问题,但在 读多写极少 的极端场景下,读写互斥仍然会导致性能浪费。
为此 JDK 8 引入了 StampedLock,提供 乐观读(Optimistic Reading)。
2.2 基本用法
StampedLock lock = new StampedLock();
// 乐观读
long stamp = lock.tryOptimisticRead();
int curX = x;
int curY = y;
if (!lock.validate(stamp)) {
// 乐观读失败,退化为悲观读
stamp = lock.readLock();
try {
curX = x;
curY = y;
} finally {
lock.unlockRead(stamp);
}
}
2.3 特性
- 乐观读:无锁访问,性能极高,但需要校验。
- 悲观读/写锁:类似
ReentrantReadWriteLock
。 - 不可重入:不同于 ReentrantLock,StampedLock 不支持可重入。
- 锁升级/降级:支持写锁降级为读锁。
2.4 适用场景
- 大量只读、极少写操作的应用。
- 对延迟敏感的高并发业务,如金融行情快照、缓存查询。
三、LockSupport:底层线程阻塞工具
3.1 定义
LockSupport
是 JUC 提供的 基础线程阻塞工具类,为构建各种锁和并发工具提供底层支持。
它比 Object.wait/notify
更灵活,避免了“先通知再等待”的经典问题。
3.2 常用方法
-
阻塞线程:
LockSupport.park(); LockSupport.parkNanos(1000000L);
-
唤醒线程:
LockSupport.unpark(thread);
3.3 特性
-
许可证模型:每个线程维护一个“许可位”,默认 0。
unpark()
设置许可位为 1;park()
消费许可位(若无许可则阻塞)。
-
无条件唤醒丢失:即使先调用
unpark()
,再调用park()
,线程也不会阻塞。
3.4 应用场景
- AQS 内部实现(ReentrantLock、Semaphore、CountDownLatch 等的基础)。
- 自定义并发控制工具。
四、三者比较
特性 | ReentrantReadWriteLock | StampedLock | LockSupport |
---|---|---|---|
读写分离 | 支持 | 支持(且有乐观读) | 不支持 |
可重入性 | 支持 | 不支持 | 不涉及锁 |
乐观锁 | 不支持 | 支持 | 不支持 |
公平性选择 | 支持 | 不支持 | 不涉及 |
使用场景 | 读多写少 | 读多写极少、延迟敏感 | 底层工具,构建并发组件基础 |
五、工程实践建议
-
优先选择 ReentrantReadWriteLock
- 在一般的读多写少场景下,语义清晰,性能稳定。
-
极致性能选择 StampedLock
- 在读远大于写、对延迟极度敏感的场景下使用。
- 注意其不可重入特性,使用时要小心避免死锁。
-
LockSupport 不直接用于业务代码
- 它是底层工具类,适合用于实现自定义同步器。
- 在业务开发中,尽量使用上层 API(如 ReentrantLock、Semaphore)。
六、总结
- ReentrantReadWriteLock:读写分离,适合常见的读多写少场景。
- StampedLock:引入乐观读机制,在读极多写极少场景下性能更佳。
- LockSupport:并发工具的底层基石,用于构建更复杂的同步器。
至此,Java 锁机制的主流体系已经完整覆盖:
synchronized
—— 内置锁,语义清晰,现代 JDK 已优化。ReentrantLock
—— 显式锁,灵活可控。ReentrantReadWriteLock
—— 读写分离,提升读性能。StampedLock
—— 乐观读,适合高读低写。LockSupport
—— 低层支撑,构建并发工具的基础。
一句话总结:
Java 的锁体系是一座分层架构,从基础的互斥语义到高阶的并发优化,再到底层的线程阻塞原语,构成了完整的并发编程工具链。
更多推荐
所有评论(0)