StampedLock
flowchart TB
subgraph StampedLock["StampedLock(JDK1.8+)"]
style StampedLock fill:#F0F2F5,stroke:#E0E6ED,rx:10,ry:10
subgraph Modes["模式分类"]
style Modes fill:#F5F7FA,stroke:#E0E6ED,rx:10,ry:10
Write(["写锁(独占):单线程持有,阻塞其他读写"]):::write
Read(["读锁(悲观读):无写锁时多线程共享"]):::read
Optimistic(["乐观读:无写锁时直接访问,提交时验证"]):::optimistic
end
subgraph Features["核心特点"]
style Features fill:#F5F7FA,stroke:#E0E6ED,rx:10,ry:10
F1(["不可重入,不支持Condition"]):::feature
F2(["性能优秀(乐观读减少阻塞)"]):::feature
F3(["适用场景:读多写少,无重入需求"]):::feature
end
end
classDef write fill:#C44545,color:#fff,rx:10,ry:10
classDef read fill:#00838F,color:#fff,rx:10,ry:10
classDef optimistic fill:#4CA497,color:#fff,rx:10,ry:10
classDef feature fill:#E99151,color:#333,rx:10,ry:10
linkStyle default stroke-width:1.5px,opacity:0.8
StampedLock 面试中问的比较少,不是很重要,简单了解即可。
StampedLock 是什么?
StampedLock 是 JDK 1.8 引入的性能更好的读写锁,不可重入且不支持条件变量 Condition。
不同于一般的 Lock 类,StampedLock 并不是直接实现 Lock或 ReadWriteLock接口,而是基于 CLH 锁 独立实现的(AQS 也是基于这玩意)。
public class StampedLock implements java.io.Serializable {
}StampedLock 提供了三种模式的读写控制模式:读锁、写锁和乐观读。
- 写锁:独占锁,一把锁只能被一个线程获得。当一个线程获取写锁后,其他请求读锁和写锁的线程必须等待。类似于
ReentrantReadWriteLock的写锁,不过这里的写锁是不可重入的。 - 读锁 (悲观读):共享锁,没有线程获取写锁的情况下,多个线程可以同时持有读锁。如果己经有线程持有写锁,则其他线程请求获取该读锁会被阻塞。类似于
ReentrantReadWriteLock的读锁,不过这里的读锁是不可重入的。 - 乐观读:允许多个线程获取乐观读以及读锁。同时允许一个写线程获取写锁。
另外,StampedLock 还支持这三种锁在一定条件下进行相互转换 。
long tryConvertToWriteLock(long stamp){}
long tryConvertToReadLock(long stamp){}
long tryConvertToOptimisticRead(long stamp){}StampedLock 在获取锁的时候会返回一个 long 型的数据戳,该数据戳用于稍后的锁释放参数,如果返回的数据戳为 0 则表示锁获取失败。当前线程持有了锁再次获取锁还是会返回一个新的数据戳,这也是StampedLock不可重入的原因。
// 写锁
public long writeLock() {
long s, next; // bypass acquireWrite in fully unlocked case only
return ((((s = state) & ABITS) == 0L &&
U.compareAndSwapLong(this, STATE, s, next = s + WBIT)) ?
next : acquireWrite(false, 0L));
}
// 读锁
public long readLock() {
long s = state, next; // bypass acquireRead on common uncontended case
return ((whead == wtail && (s & ABITS) < RFULL &&
U.compareAndSwapLong(this, STATE, s, next = s + RUNIT)) ?
next : acquireRead(false, 0L));
}
// 乐观读
public long tryOptimisticRead() {
long s;
return (((s = state) & WBIT) == 0L) ? (s & SBITS) : 0L;
}StampedLock 的性能为什么更好?
相比于传统读写锁多出来的乐观读是StampedLock比 ReadWriteLock 性能更好的关键原因。StampedLock 的乐观读允许一个写线程获取写锁,所以不会导致所有写线程阻塞,也就是当读多写少的时候,写线程有机会获取写锁,减少了线程饥饿的问题,吞吐量大大提高。
StampedLock 适合什么场景?
和 ReentrantReadWriteLock 一样,StampedLock 同样适合读多写少的业务场景,可以作为 ReentrantReadWriteLock的替代品,性能更好。
不过,需要注意的是StampedLock不可重入,不支持条件变量 Condition,对中断操作支持也不友好(使用不当容易导致 CPU 飙升)。如果你需要用到 ReentrantLock 的一些高级性能,就不太建议使用 StampedLock 了。
另外,StampedLock 性能虽好,但使用起来相对比较麻烦,一旦使用不当,就会出现生产问题。强烈建议你在使用StampedLock 之前,看看 StampedLock 官方文档中的案例。
StampedLock 的底层原理了解吗?
StampedLock 不是直接实现 Lock或 ReadWriteLock接口,而是基于 CLH 锁 实现的(AQS 也是基于这玩意),CLH 锁是对自旋锁的一种改良,是一种隐式的链表队列。StampedLock 通过 CLH 队列进行线程的管理,通过同步状态值 state 来表示锁的状态和类型。
StampedLock 的原理和 AQS 原理比较类似,这里就不详细介绍了,感兴趣的可以看看下面这两篇文章:
如果你只是准备面试的话,建议多花点精力搞懂 AQS 原理即可,StampedLock 底层原理在面试中遇到的概率非常小。
评论
使用 GitHub 账号即可参与加载较慢?可 直接前往 GitHub Discussions 查看与参与。