StampedLock简介
StampedLock实现了不仅多个读不互相阻塞,同时在读操作时不会阻塞写操作。
StampedLock核心思想在于,在读的时候如果发生了写,应该通过重试的方式来获取新的值,而不应该阻塞写操作。
这种模式也就是典型的无锁编程思想,和CAS自旋的思想一样。
这种操作方式决定了StampedLock在读线程非常多而写线程非常少的场景下非常适用,同时还避免了写饥饿情况的发生。
StampedLock中引入了一个stamp(邮戳)的概念。它代表线程获取到锁的版本,每一把锁都有一个唯一的stamp。
StampedLock提供了乐观读锁,可取代ReadWriteLock以进一步提升并发性能;StampedLock是不可重入锁。
StampedLock特点
StampedLock的主要特点概括一下,有以下几点:
- 所有获取锁的方法,都返回一个邮戳(stamp),stamp为0表示获取失败,其余都表示成功;
- 所有释放锁的方法,都需要一个邮戳(stamp),这个stamp必须是和成功获取锁时得到的stamp一致;
- StampedLock是不可重入的;(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
- StampedLock有三种访问模式:
- ①Reading(读模式):功能和ReentrantReadWriteLock的读锁类似
- ②Writing(写模式):功能和ReentrantReadWriteLock的写锁类似
- ③Optimistic reading(乐观读模式):这是一种优化的读模式。
- StampedLock支持读锁和写锁的相互转换
我们知道RRW中,当线程获取到写锁后,可以降级为读锁,但是读锁是不能直接升级为写锁的。
StampedLock提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。 - 无论写锁还是读锁,都不支持Conditon等待
锁状态
StampedLock提供了写锁、悲观读锁、乐观读锁三种模式的锁
- 如何维护锁状态呢?
StampedLock的锁状态用 long 类型的 state 表示,类似ReentrantReadWriteLock,通过将 state 按位切分的方式表示不同的锁状态。
- 悲观读锁:state 的前 7 位(0-7 位)表示获取读锁的线程数,如果超过 0-7 位的最大容量 126,则使用一个名为
readerOverflow的 int 整型保存超出数。- 写锁:state 第 8 位为写锁标志,0 表示未被占用,1 表示写锁被占用。state 第 8-64 位表示写锁的获取次数,次数超过 64 位最大容量则重新从 1 开始。
- 乐观读锁:不需要维护锁状态,但是在具体操作数据前要检查一下自己操作的数据是否经过修改操作,也就是验证是否有线程获取过写锁。
写锁writeLock
写锁writeLock,是排它锁、也叫独占锁,相同时间只能有一个线程获取锁,其他线程请求读锁和写锁都会被阻塞。功能类似于ReetrantReadWriteLock.writeLock。
区别是StampedLock的写锁是不可重入锁。当前没有线程持有读锁或写锁的时候才可以获得获取到该锁。
writeLock使用
// writeLock使用
public void writeLockTest() {
//创建StampedLock对象
StampedLock sl = new StampedLock();
//获取写锁,并且返回stamp
long stamp = sl.writeLock();
System.out.println("get write lock,stamp=" + stamp);
//使用完毕,释放锁,但是要传入对应的stamp
sl.unlockWrite(stamp);
//再次获取写锁,并获得新的stamp
stamp = sl.writeLock();
System.out.println("get write lock,stamp=" + stamp);
//释放写锁
sl.unlockWrite(stamp);
}
//print
get write lock,stamp=384
get write lock,stamp=640
说明:writeLock与unlockWrite必须成对儿使用,解锁时必须需要传入相对应的stamp才可以释放锁。每次获得锁之后都会得到一个新stamp值。
非重入锁示例
// 非重入锁
public void nonReentrantLock() {
StampedLock sl = new StampedLock();
//第一次获得写锁
long stamp = sl.writeLock();
System.out.println("get write lock,stamp="+stamp);
//第一次获得写锁还未释放,又来获取写锁,是否能够获取到?
//如果是重入锁则可以获取到,如果不是则获取不到
stamp = sl.writeLock();
System.out.println("get write lock,stamp="+stamp);
//释放锁
sl.unlockWrite(stamp);
}
//print
get write lock,stamp=384
...
说明:同一个线程获取锁后,再次尝试获取锁而无法获取,则证明其为非重入锁。
重入锁示例
// 重入锁
public static void reentrantLock() {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
//第一次获得写锁
reentrantReadWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName().concat("get write lock"));
//第一次获得写锁还未释放,又来获取写锁,是否能够获取到?
//如果是重入锁则可以获取到,如果不是则获取不到
reentrantReadWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName().concat("get write lock again"));
//释放锁
reentrantReadWriteLock.writeLock().unlock();
reentrantReadWriteLock.writeLock().unlock();
}
说明:对于ReentrantReadWriteLock.WriteLock,同一个线程获取写锁后,再次尝试获取锁依然可获取锁,则证明其为重入锁。
ReentrantLock VS ReentrantReadWriteLock VS StampedLock
| 锁 | 特性 | 是否支持重入 | 是否支持锁升级 | 适合场景 |
|---|---|---|---|---|
| ReentrantLock | 独占可重入 | 是 | 无 | 纯写入 |
| ReentrantReadWriteLock | 非独占可重读,读写锁,悲观锁 | 是 | 否 | 读写均衡 |
| StampedLock | 非独占不可重入,多模式锁,乐观锁 | 否 | 是 | 读多写少 |