华为应⽤锁退出⽴即锁_读写锁应⽤与原理
⽬录:
概念
应⽤
原理
StampedLock
⼀.概念
独占锁:指该锁⼀次只能被⼀个线程所持有。对ReentrantLock和Synchronized⽽⾔都是独占锁
共享锁:指该锁可以被多个线程锁持有
对ReentrantReadWriteLock其读锁是共享,其写锁是独占
写的时候只能⼀个⼈写,但是读的时候,可以多个⼈同时读
为什么会有写锁和读锁
原来我们使⽤ReentrantLock创建锁的时候,是独占锁,也就是说⼀次只能⼀个线程访问,但是有⼀个读写分离场景,读的时候想同时进
⾏,因此原来独占锁的并发性就没这么好了,因为读锁并不会造成数据不⼀致的问题,因此可以多个⼈共享读
多个线程同时读⼀个资源类没有任何问题,所以为了满⾜并发量,读取共享资源应该可以同时进⾏,但是如果⼀个线程想去写共享资源,
就不应该再有其它线程可以对该资源进⾏读或写
读-读:能共存
读-写:不能共存
写-写:不能共存
ReentrantReadWriteLock
当读操作远远⾼于写操作时,这时候使⽤读写锁让读-读可以并发,提⾼性能。类似于数据库中的lect...from...lockinshare
mode
提供⼀个数据容器类内部分别使⽤读锁保护数据的read()⽅法,写锁保护数据的write()⽅法
代码
classDataContainer{privateObjectdata;privateReentrantReadWriteLockrw=newReentrantReadWriteLock();ckr
测试读锁-读锁可以并发
DataContainerdataContainer=newDataContainer();newThread(()->{();},"t1").start();newThread(()->{();},"t2").star
输出结果,从这⾥可以看到Thread-0锁定期间,Thread-1的读操作不受影响
14:05:ntainer[t2]-获取读锁...14:05:ntainer[t1]-获取读锁...14:05:ntainer[t1]-读取14:05:nt
测试读锁-写锁相互阻塞
DataContainerdataContainer=newDataContainer();newThread(()->{();},"t1").start();(100);newThread(()->{dataContaine
输出结果
14:04:ntainer[t1]-获取读锁...14:04:ntainer[t2]-获取写锁...14:04:ntainer[t2]-写⼊14:04:nt
写锁-写锁也是相互阻塞的,这⾥就不测试了
注意事项
读锁不⽀持条件变量
重⼊时升级不⽀持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
();try{//...();try{//...}finally{();}}finally{();}
重⼊时降级⽀持:即持有写锁的情况下去获取读锁
classCachedData{Objectdata;//是否有效,如果失效,需要重新计算datavolatilebooleancacheValid;finalReentrantReadWriteLockrwl=newReentrantRe
⼆.读写锁应⽤
1.缓存更新策略更新时,是先清缓存还是先更新数据库
先清缓存
先更新数据库
补充⼀种情况,假设查询线程A查询数据时恰好缓存数据由于时间到期失效,或是第⼀次查询
这种情况的出现⼏率⾮常⼩,见facebook论⽂
2.读写锁实现⼀致性缓存
使⽤读写锁实现⼀个简单的按需加载缓存
classGenericCachedDao{//HashMap作为缓存⾮线程安全,需要保护HashMapmap=newHashMap<>();ReentrantReadWriteLocklock=newReentrantRea
注意
以上实现体现的是读写锁的应⽤,保证缓存和数据库的⼀致性,但有下⾯的问题没有考虑
适合读多写少,如果写操作⽐较频繁,以上实现性能低没有考虑缓存容量
没有考虑缓存过期
只适合单机
并发性还是低,⽬前只会⽤⼀把锁
更新⽅法太过简单粗暴,清空了所有key(考虑按类型分区或重新设计key)
乐观锁实现:⽤CAS去更新
三.读写锁原理
1.图解流程
读写锁⽤的是同⼀个Sycn同步器,因此等待队列、state等也是同⼀个
,
1)t1成功上锁,流程与ReentrantLock加锁相⽐没有特殊之处,不同是写锁状态占了state的低16位,⽽读锁使⽤的是state的⾼
16位
2)t2执⾏,这时进⼊读锁的eShared(1)流程,⾸先会进⼊tryAcquireShared流程。如果有写锁占据,那么
tryAcquireShared返回-1表⽰失败
tryAcquireShared返回值表⽰
-1表⽰失败
0表⽰成功,但后继节点不会继续唤醒
正数表⽰成功,⽽且数值是还有⼏个后继节点需要唤醒,读写锁返回1
3)这时会进⼊ireShared(1)流程,⾸先也是调⽤addWaiter添加节点,不同之处在于节点被设置为模式
⽽⾮IVE模式,注意此时t2仍处于活跃状态
4)t2会看看⾃⼰的节点是不是⽼⼆,如果是,还会再次调⽤tryAcquireShared(1)来尝试获取锁
5)如果没有成功,在doAcquireShared内for(;;)循环⼀次,把前驱节点的waitStatus改为-1,再for(;;)循环⼀次尝试
tryAcquireShared(1)如果还不成功,那么在parkAndCheckInterrupt()处park
,
这种状态下,假设⼜有t3加读锁和t4加写锁,这期间t1仍然持有锁,就变成了下⾯的样⼦
这时会⾛到写锁的e(1)流程,调⽤ea(1)成功,变成下⾯的样⼦
接下来执⾏唤醒流程Successor,即让⽼⼆恢复运⾏,这时t2在doAcquireShared内parkAndCheckInterrupt()处恢复
运⾏
这回再来⼀次for(;;)执⾏tryAcquireShared成功则让读锁计数加⼀
这时t2已经恢复运⾏,接下来t2调⽤tHeadAndPropagate(node,1),它原本所在节点被置为头节点
事情还没完,在tHeadAndPropagate⽅法内还会检查下⼀个节点是否是shared,如果是则调⽤doReleaShared()将head的状态
从-1改为0并唤醒⽼⼆,这时t3在doAcquireShared内parkAndCheckInterrupt()处恢复运⾏
这回再来⼀次for(;;)执⾏tryAcquireShared成功则让读锁计数加⼀
这时t3已经恢复运⾏,接下来t3调⽤tHeadAndPropagate(node,1),它原本所在节点被置为头节点
下⼀个节点不是shared了,因此不会继续唤醒t4所在节点,
t2进⼊eShared(1)中,调⽤tryReleaShared(1)让计数减⼀,但由于计数还不为零
t3进⼊eShared(1)中,调⽤tryReleaShared(1)让计数减⼀,这回计数为零了,进⼊doReleaShared()将头节点从-
1改为0并唤醒⽼⼆,即
之后t4在acquireQueued中parkAndCheckInterrupt处恢复运⾏,再次for(;;)这次⾃⼰是⽼⼆,并且没有其他竞
争,tryAcquire(1)成功,修改头结点,流程结束
2.源码分析
写锁上锁流程
staticfinalclassNonfairSyncextendsSync{//...省略⽆关代码//外部类WriteLock⽅法,⽅便阅读,放在此处publicvoidlock(){e(1);}//AQS继承
写锁释放流程
staticfinalclassNonfairSyncextendsSync{//...省略⽆关代码//WriteLock⽅法,⽅便阅读,放在此处publicvoidunlock(){e(1);}//AQS继承过
读锁上锁流程
staticfinalclassNonfairSyncextendsSync{//ReadLock⽅法,⽅便阅读,放在此处publicvoidlock(){eShared(1);}//AQS继承过来的⽅法,⽅
读锁释放流程
staticfinalclassNonfairSyncextendsSync{//ReadLock⽅法,⽅便阅读,放在此处publicvoidunlock(){eShared(1);}//AQS继承过来的⽅法,⽅
StampedLock
该类⾃JDK8加⼊,是为了进⼀步优化读性能,它的特点是在使⽤读锁、写锁时都必须配合【戳】使⽤
加解读锁
longstamp=ck();Read(stamp);
加解写锁
longstamp=ock();Write(stamp);
乐观读,StampedLock⽀持tryOptimisticRead()⽅法(乐观读),读取完毕后需要做⼀次戳校验如果校验通过,表⽰这期间确实没有写
操作,数据可以安全使⽤,如果校验没通过,需要重新获取读锁,保证数据安全。
longstamp=imisticRead();//验戳if(!te(stamp)){//锁升级}
提供⼀个数据容器类内部分别使⽤读锁保护数据的read()⽅法,写锁保护数据的write()⽅法
classDataContainerStamped{privateintdata;privatefinalStampedLocklock=newStampedLock();publicDataContainerStamped(intdata){
测试读-读可以优化
publicstaticvoidmain(String[]args){DataContainerStampeddataContainer=newDataContainerStamped(1);newThread(()->{(1);},
输出结果,可以看到实际没有加读锁
15:58:ntainerStamped[t1]-optimisticreadlocking...25615:58:ntainerStamped[t2]-optimisticreadlocking...25615:58
测试读-写时优化读补加读锁
publicstaticvoidmain(String[]args){DataContainerStampeddataContainer=newDataContainerStamped(1);newThread(()->{(1);},"
输出结果
15:57:ntainerStamped[t1]-optimisticreadlocking...25615:57:ntainerStamped[t2]-writelock38415:57:
注意StampedLock不⽀持条件变量
StampedLock不⽀持可重⼊
本文发布于:2022-11-23 16:23:59,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/6813.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |