目录
本系列教程传送门👉 《Java高并发实战:原理、源码与性能调优》
StampedLock 是 Java 8 引入的一种高性能读写锁,比 ReentrantReadWriteLock
更灵活,支持乐观读、悲观读和写锁三种模式。其底层实现基于 CLH 队列变种 + CAS 操作 + 状态机控制,适用于读多写少的高并发场景。
1. StampedLock 的核心特点
1.1 三种访问模式
StampedLock 提供三种锁模式:
-
写锁(Write Lock)
-
独占锁,类似
ReentrantReadWriteLock
的写锁。 -
获取写锁后,其他线程无法获取读锁或写锁。
-
返回一个
long
类型的 Stamp,用于解锁或升级锁。
-
-
悲观读锁(Pessimistic Read Lock)
-
共享锁,类似
ReentrantReadWriteLock
的读锁。 -
允许多个线程同时获取读锁,但会阻塞写锁。
-
返回一个
long
类型的 Stamp,用于解锁。
-
-
乐观读(Optimistic Read)
-
无锁机制,仅返回一个 Stamp 用于后续验证。
-
乐观读期间,允许其他线程获取写锁。
-
读取数据后,需调用
validate(stamp)
检查是否有写操作冲突。
-
1.2 锁升级与降级
-
锁升级(读锁 → 写锁):
long stamp = lock.readLock();
try {
// 尝试升级为写锁
long writeStamp = lock.tryConvertToWriteLock(stamp);
if (writeStamp != 0L) {
stamp = writeStamp; // 升级成功
} else {
lock.unlockRead(stamp); // 升级失败,释放读锁并重新获取写锁
stamp = lock.writeLock();
}
} finally {
lock.unlock(stamp);
}
锁降级(写锁 → 读锁):
long stamp = lock.writeLock();
try {
// 降级为读锁
stamp = lock.tryConvertToReadLock(stamp);
} finally {
lock.unlock(stamp);
}
2. 核心设计思想
StampedLock 的核心优化点:
-
乐观读(Optimistic Read):不阻塞其他读/写操作,仅通过版本号(Stamp)校验数据是否被修改。
-
无重入性:不同于
ReentrantReadWriteLock
,StampedLock 不支持锁重入,避免死锁风险。 -
低开销:通过位运算管理锁状态,减少内存占用。
StampedLock 的底层采用 CLH 队列变种(类似 AQS),并结合 自旋优化 提高性能。
2.1 核心数据结构
-
state
(long 类型):-
低 7 位:读锁计数(最多 126 个读锁)。
-
第 8 位:写锁标记(
WBIT = 0x80
)。 -
其余位:版本号(用于乐观读检查)。
-
-
等待队列(CLH 变种):
-
采用链表结构管理等待线程。
-
写锁优先,避免读锁饥饿。
-
2.2 关键方法实现
(1)获取写锁(writeLock)
public long writeLock() {
long s, next;
if (((s = state) & ABITS) == 0L) { // 无锁状态
if (U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) // CAS 设置写锁位
return next;
}
return acquireWrite(false, 0L); // 竞争失败,进入队列等待
}
-
如果当前无锁(
state & ABITS == 0
),直接 CAS 设置WBIT
。 -
否则,进入
acquireWrite
自旋或阻塞。
(2)获取读锁(readLock)
public long readLock() {
long s, next;
if ((s = state) & ABITS) < RFULL) { // 读锁未满
if (U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) // CAS 增加读计数
return next;
}
return acquireRead(false, 0L); // 竞争失败,进入队列等待
}
-
如果读锁未满(
< RFULL
),直接 CAS 增加读计数。 -
否则,进入
acquireRead
自旋或阻塞。
(3)乐观读(tryOptimisticRead)
public long tryOptimisticRead() {
long s;
return ((s = state) & WBIT) == 0L ? (s & SBITS) : 0L;
}
-
如果没有写锁(
WBIT == 0
),返回当前版本号(s & SBITS
)。 -
否则返回 0(表示乐观读失败)。
(4)验证乐观读(validate)
public boolean validate(long stamp) {
return (stamp & SBITS) == (state & SBITS);
}
-
检查乐观读期间是否有写操作(版本号是否变化)。
3. StampedLock 性能优化
3.1 乐观读避免加锁
double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead(); // 乐观读
double x = x, y = y;
if (!sl.validate(stamp)) { // 检查是否有写操作
stamp = sl.readLock(); // 冲突则获取悲观读锁
try {
x = x;
y = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(x * x + y * y);
}
-
乐观读失败时,才降级为悲观读锁。
3.2 自旋优化
-
在竞争不激烈时,采用 自旋 减少线程切换开销。
-
如果自旋失败,再进入 CLH 队列 阻塞。
4. StampedLock 的局限性
-
不支持重入:
-
同一线程重复获取锁会导致死锁。
-
必须确保锁的释放。
-
-
没有 Condition 支持:
-
必须结合
LockSupport
或wait/notify
实现条件等待。
-
-
容易导致饥饿:
-
写锁优先,可能导致读锁长时间等待。
-
5. 适用场景
✅ 读多写少(如缓存、统计计数)。
✅ 乐观读 可提高无竞争时的性能。
❌ 不适合写多读少(ReentrantReadWriteLock
可能更好)。
❌ 不适合需要 Condition 的场景(用 ReentrantLock
替代)。
6. 总结
特性 | StampedLock | ReentrantReadWriteLock |
---|---|---|
锁模式 | 写锁、悲观读、乐观读 | 写锁、读锁 |
重入性 | ❌ 不支持 | ✅ 支持 |
Condition | ❌ 不支持 | ✅ 支持 |
性能 | ✅ 更高(乐观读优化) | ⚠️ 一般 |
适用场景 | 读多写少 | 通用 |
推荐使用场景:
-
高并发读操作(如缓存)。
-
乐观读能显著减少锁竞争时。
慎用场景:
-
需要
Condition
或重入锁时。 -
写操作频繁时。
7. 示例代码
import java.util.concurrent.locks.StampedLock;
public class StampedLockDemo {
private final StampedLock sl = new StampedLock();
private int x, y;
public void move(int deltaX, int deltaY) {
long stamp = sl.writeLock();
try {
x += deltaX;
y += deltaY;
} finally {
sl.unlockWrite(stamp);
}
}
public double distanceFromOrigin() {
long stamp = sl.tryOptimisticRead();
int currentX = x, currentY = y;
if (!sl.validate(stamp)) {
stamp = sl.readLock();
try {
currentX = x;
currentY = y;
} finally {
sl.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
结论
StampedLock 是一种高性能锁,适用于 读多写少 的场景,乐观读 是其最大优势。但它 不支持 Condition 和重入,使用时需谨慎。
专栏合集在这里:
👉 《Java高并发实战:原理、源码与性能调优》(进行中)
👉 《Java集合框架深度解析:从源码到实战应用》(已完结)
📣 互动邀请
如果本文对您有帮助,请不要吝啬您的支持:
👍 点赞 - 您的点赞是我持续创作的最大动力!
🔔 关注 - 获取更多Java核心技术解析和实战技巧
💬 评论 - 有任何问题或建议欢迎留言讨论
🚀 下期预告
接下来我将继续深入讲解:
《高并发必备(十)!Condition接口使用技巧与AQS底层原理剖析》