1 简介
StampedLock是JDK1.8中新增的一个读写锁,也是对JDK1.5中的读写锁ReentrantReadWriteLock的优化。在原先读写锁的基础上新增了一种叫乐观读(Optimistic Reading)的模式。该模式并不会加锁,所以不会阻塞线程,会有更高的吞吐量和更高的性能。
ReentrantReadWriteLock介绍
:https://www.cnblogs.com/jthr/p/16179865.html
2 特性及注意事项
2.1 三种访问数据模式
1)Writing(独占写锁):writeLock
方法会使线程阻塞等待独占访问,同一时刻有且只有一个写线程获取锁资源,功能和ReentrantReadWriteLock
的读类似,
2)Reading(悲观读锁):readLock
方法,读读共享,读写互斥。功能和ReentrantReadWriteLock的写锁类似
3)Optimistic Reading(乐观读):Optimistic reading(乐观读模式):无锁机制,类似于数据库中的乐观锁,支持读写并发,很乐观认为读取时没人修改,假如被修改再实现升级为悲观读模式。
2.2 支持读写锁相互转换
ReentrantReadWriteLock
当线程获取写锁后可以降级成读锁,但是反过来则不行。
StampedLock
提供了读锁和写锁相互转换的功能,使得该类支持更多的应用场景。
2.3 注意事项
1)
StampedLock是不可重入的,危险(如果一个线程已经持有了写锁,再去获取写锁的话就会造成死锁)
2)StampedLock 的悲观读锁和写锁都不支持条件变量(Condition),这个也需要注意。
3)所有获取锁的方法,都返回一个邮戳(Stamp),Stamp为零表示获取失败,其余都表示成功;
4)
所有释放锁的方法,都需要一个邮戳(Stamp),这个Stamp必须是和成功获取锁时得到的Stamp一致;
5)使用 StampedLock一定不要调用中断操作,即不要调用interrupt() 方法,如果需要支持中断功能,一定使用可中断的悲观读锁 readLockInterruptibly()和写锁writeLockInterruptibly()
3 StampedLock
比ReentrantReadWriteLock效率高
关键在于StampedLock
提供的乐观读,我们知道ReentrantReadWriteLock
支持多个线程同时获取读锁,但是当多个线程同时读的时候,所有的写线程都是阻塞的。
StampedLock
的乐观读允许一个写线程获取写锁,所以不会导致所有写线程阻塞,也就是当读多写少的时候,写线程有机会获取写锁,减少了线程饥饿的问题,吞吐量大大提高。同时允许多个乐观读和一个先线程同时进入临界资源操作,那读取的数据可能是错的怎么办?
是的,乐观读不能保证读取到的数据是最新的,所以将数据读取到局部变量的时候需要通过 lock.validate(stamp)
校验下是否被写线程修改过,若是修改过则需要上悲观读锁,再重新读取数据到局部变量。
同时由于乐观读并不是锁,所以没有线程唤醒与阻塞导致的上下文切换,性能更好。
4 示例
public class StanpedlockLockTest1 { private double x, y; private final StampedLock sl = new StampedLock(); //写锁-排他锁 void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } //下面看看乐观读锁案例 double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁 double 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); } //下面是悲观读锁转写锁 void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (true) { long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁 if (ws != 0L) { //这是确认转为写锁是否成功 stamp = ws; //如果成功 替换票据 x = newX; //进行状态改变 y = newY; //进行状态改变 break; } else { //如果不能成功转换为写锁 sl.unlockRead(stamp); //我们显式释放读锁 stamp = sl.writeLock(); //显式直接进行写锁 然后再通过循环再试 } } } finally { sl.unlock(stamp); //释放读锁或写锁 } } }
5 使用场景
对于读多写少的高并发场景 StampedLock
的性能很好,通过乐观读模式很好的解决了写线程“饥饿”的问题,我们可以使用StampedLock
来代替ReentrantReadWriteLock