当前位置: 首页 > 科技观察

都2023年了,你还不知道StampedLock吗?

时间:2023-03-22 10:59:41 科技观察

概述ReentrantReadWriteLock可能是您想到读写锁时首先想到的。其实在jdk8之后,java提供了一个性能更好的读写锁并发类StampedLock。该类的初衷是作为一个内部工具类,辅助其他线程安全组件的开发。如果用得好,这个类可以提高系统性能,如果用得不好,很容易出现死锁等莫名其妙的问题。本文主要是和大家一起学习StampedLock的功能和使用。StampedLock简介StampedLock的状态由版本和模式组成。锁获取方法返回一个标记,表示并控制对锁状态的访问。StampedLock提供了3种模式来控制访问锁:写模式获取写锁,是独占的,当锁处于写模式时,无法获取读锁,所有乐观读验证都会失败。writeLock():阻塞等待独占锁,返回一个戳记,如果为0则表示获取失败。tryWriteLock():尝试获取写锁并返回一个戳记,如果为0则表示获取失败。longtryWriteLock(longtime,TimeUnitunit):尝试获取独占写锁,可以等待一段时间的事件,返回一个戳记,为0表示获取失败。longwriteLockInterruptibly():尝试获取独占写锁,可中断并返回一个戳记,为0表示获取失败。unlockWrite(longstamp):释放独占写锁,传入之前获取的stamp。tryUnlockWrite():如果持有写锁,则在不标记值的情况下释放锁。这种方法可能对错误后的恢复很有用。longstamp=lock.writeLock();try{....}finally{lock.unlockWrite(stamp);}读模式悲观方式去非独占读锁之后。readLock():等待获取非独占读锁的块,返回一个戳记,如果为0则表示获取失败。tryReadLock():尝试获取读锁并返回一个戳记,如果为0则表示获取失败。longtryReadLock(longtime,TimeUnitunit):尝试获取读锁,可以等待一段时间的事件,返回一个戳记,如果为0则表示获取失败。longreadLockInterruptibly():阻塞等待获取非独占读锁,可中断并返回一个戳记,为0表示获取失败。unlockRead(longstamp):释放非独占读锁,传入之前获取的stamp。tryUnlockRead():如果持有读锁,则释放一次,不标记值。这种方法可能对错误后的恢复很有用。longstamp=lock.readLock();try{....}finally{lock.unlockRead(stamp);}乐观读模式乐观读是指如果读操作多,写操作少,可以乐观的,同时写和读是很少见的,所以不悲观的使用完全读锁。程序可以在读取数据后检查是否被写入执行改变,然后采取后续措施(重新读取改变信息,或者抛出异常),这个小小的改进可以大大提高程序的吞吐量。StampedLock支持tryOptimisticRead()方法。看完后做个戳验证。如果验证通过,说明这段时间没有其他线程的写操作,数据可以安全使用。如果验证失败,则需要重新获取读锁。确保数据一致性。tryOptimisticRead():返回一个可以稍后验证的戳记,如果锁是独占的则返回零。booleanvalidate(longstamp):如果自给定戳记发出以来尚未独占获取锁,则返回true。longstamp=lock.tryOptimisticRead();//验证戳if(!lock.validate(stamp)){//锁升级}另外StampedLock提供了api实现以上三种方式的转换:longtryConvertToWriteLock(longstamp)if如果锁定状态与给定的标记匹配,则执行以下操作之一。如果戳记表明持有写锁,则将其返回。或者,在读锁的情况下,释放读锁并在写锁可用时返回写标记。或者,在乐观读取的情况下,仅在立即可用时返回写入标记。在所有其他情况下,该方法返回零longtryConvertToReadLock(longstamp)如果锁定状态与给定的标记匹配,请执行以下操作之一。如果戳记表明持有写锁,则释放它并获取读锁。或者,如果它是读锁,则返回它。或者,在乐观读取的情况下,获取读取锁并仅在立即可用时才返回读取标记。在所有其他情况下,该方法返回零。longtryConvertToOptimisticRead(longstamp)如果锁定状态与给定的戳记匹配,则如果该戳记表明锁已被持有,则释放它并返回一个观察戳记。或者,在乐观读取的情况下,在验证后返回它。此方法在所有其他情况下返回0,因此可用作“tryUnlock”的一种形式。演示示例下面通过一个示例来演示StampedLock的使用。示例来自jdk中的javadoc。@Slf4j@DatapublicclassPoint{privatedoublex,y;私人最终StampedLocksl=newStampedLock();voidmove(doubledeltaX,doublethrows{//涉及修改共享资源,使用写锁独占操作longstamp=sl.writeLock();log.info("writeLock锁成功");Thread.sleep(500);try{x+=deltaX;y+=deltaY;}finally{sl.unlockWrite(stamp);log.info("unlockwritelocksuccess");}}/***使用乐观读锁访问共享资源*注意:乐观读锁需要将一个要操作的变量复制到方法栈中,保证数据的一致性,而在操作数据的时候,可能其他写线程修改了数据,*而我们操作的是方法栈中的数据,即一个snapshot,所以返回的数据最多的不是最新的数据,但是一致性还是有保证的。**@return*/doubledistanceFromOrigin()throws{longstamp=sl.tryOptimisticRead();//使用乐观读锁log.info("tryOptimisticReadlocksuccess");//在Thread.sleep(1000)中休眠一秒钟;双currentX=x,currentY=y;//将共享资源复制到本地方法栈if(!sl.validate(stamp)){//如果写锁被占用,可能会导致数据不一致,所以切换到普通读锁模式log.info("validate邮票错误");邮票=sl.readLock();log.info("读锁成功");尝试{currentX=x;当前Y=y;}最后{sl.unlockRead(stamp);log.info("解锁读取成功");}}返回Math.sqrt(currentX*currentX+currentY*currentY);}voidmoveIfAtOrigin(doublenewX,double{//upgrade//可以从乐观开始,而不是读取模式longstamp=sl.readLock();try{while(x==0.0&&am;y==0.0){longws=sl.tryConvertToWriteLock(stamp);//将读锁转换为写锁if(ws!=0L){stamp=ws;x=新X;y=新Y;休息;}else{sl.unlockRead(邮票);邮票=sl.writeLock();}}}最后{sl.unlock(stamp);}}}测试用例:@TestpublicvoidtestStamped()throwsInterruptedException{Pointpoint=newPoint();point.setX(1);点.setY(2);//线程0执行了乐观读取Thread0=newThread(()->{try{//乐观读取point.distanceFromOrigin();}catch(InterruptedExceptione){e.printStackTrace();}},"thread-0");thread0.start();线程.睡眠(500);//线程1执行写锁Threadthread1=newThread(()->{//乐观读try{point.move(3,4);}catch(InterruptedExceptione){e.printStackTrace();}},"线程1");thread1.start();thread0.join();thread1.join();}结果:性能对比是由于StampedLockStampedLock的乐观读模式一直都是高性能高吞吐,那么具体的性能提升有多少呢?下图可以看出,与ReadWritLock相比,在一个线程的情况下,读取速度大约是4倍,写入速度是1倍。下图可以看出,在16线程的情况下,读性能是几十倍,写性能是近10倍。:下图是吞吐量提升:那么这是否意味着StampedLock可以全面替代ReentrantReadWriteLock,答案是否定的,StampedLock相比ReentrantReadWriteLock有以下两个问题:不支持条件变量Condition不支持支持可重入所以最终选择StampedLock还是ReentrantReadWriteLock还是要看具体的业务场景。总结本文主要讲解StampedLock的功能和使用。至于原理,StampedLock虽然没有像其他锁一样定义内部类来实现AQS框架,但是StampedLock的基本实现思路是利用CLH队列进行线程管理和同步状态值。来指明锁的状态和类型,具体的源码实现,有兴趣的可以自行追踪。