ReentrantReadWriteLock原理详解

当读操作远远高于写操作时,这时候使用读写锁让读——读可以并发,提高性能。

类似于数据库中的 select ., from .. lock in share mode 

提供一个数据容器类内部分别使用读锁保护数据的 read ()方法,写锁保护数据的 write ()方法

简单来说,就是读锁可以共享,但是写锁必须独占

一、 ReentrantReadWriteLock

JUC为我们提供了一个可用于读和写的读写锁。

简单看下使用方法:

        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
        ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

可以看到,读写锁使用的是一个Sync同步器(使用一个对象),可以分别创建。

注意事项

1、读锁不支持条件变量

2、重入时支持升级,即当前线程如果持有读锁,则在重入获得锁时不能在获得写锁

3、冲入支持降级,即当前线程如果持有写锁,则在重入时可以获得读锁

二、读写锁原理

加锁

读写锁用的是同一个Sync同步器,所以有相同的阻塞队列和state

 

一、如图,此时线程t1占有锁流程与之前讲的ReentrantLock相似,不同之处时写锁状态占了state的低16位,而读锁占了state的高16位。

同ReentrantLock类似,t1同时也修改了state位的值,表示该锁已被占有。

二、此时出现t2竞争,执行 r.lock() ,这时进入读锁的 sync . acquireShared (1)流程

首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared 

1、返回-1表 示失败 

2、0表示成功,但后继节点不会继续唤醒 

3、正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回1

此处由于t1占据锁,所以此处t2的尝试是失败的,于是返回-1。

注意

此时如果占据写锁的是t2线程,则获取读锁将会成功(锁降级原理)

三、这时会进入 sync . doAcquireShared (1)流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为 Node . SHARED 模式(此处我们假设t2要获取的是读锁)而非 Node . EXCLUSIVE(如果尝试获取的是写锁) 模式,注意此时t2仍处于活跃状态

四、口会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared (1)来尝 
试获取锁

五、如果没有成功,在 doAcquireShared 内 for (;;)循环一次,把前驱节点的 waitStatus 改为-1(同ReentrantLock,用来表示前一节点有义务唤醒该线程)。

六、此时如果依然有线程来争夺锁资源,则重复t2竞争时的策略,加入到队列中

解锁

当t1线程结束对锁的占用时,会释放锁对象,此时会触发解锁流程。

这时会走到写锁的 sync . release (1)流程,调用 sync . tryRelease (1)成功接下来执行唤醒流程 sync . unparkSuccessor ,即让老二恢复运行,这t2 doAcquireShared 内 parkAndCheckInterrupt )处恢复运行这回再来一次 for (; ;)执行 tryAcquireShared 成功则让读锁计数加一

这时12已经恢复运行,接下来1调用 setHeadAndPropagate ( node ,1),它原本所在节点 
被置为头节点

事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared ,如果是则调用 doReleaseShared )将 head 的状态从-1改为0并唤醒老二,这时3在 doAcquireShared 内parkAndCheckInterupt0处恢复运行。同时使读锁计数加一。直到遇到Ex节点为止。

简单来说,当一个线程释放锁时,回在队列中的线程就会开始抢占锁资源,如果排在第二位(第一位是占位节点)的节点是Shared,由于读锁是共享锁,所以第二位的线程获得读锁以后,会继续检查其他线程的状态,如果还是Shared,则继续使线程获得锁,直到遇到Ex线程为止。但此时的exclusiveOwner处还是空的(因为此时的Sync是读锁)。

如果第二个节点为Ex(即需要获得的是写锁,则仅仅使该线程获得锁,exclusiveOwner指向该线程。)

总结

综上我们可以看出,ReentrantReadWriteLock是一个读写双用锁,但是除非是同一个线程的重入,ReentrantReadWriteLock不可能同时有读锁和写锁的状态(某一时刻只能是读锁或写锁)。

成为读锁时,只增加state计数,而exclucsiveOwner处依然指向空。

成为写锁时,exclucsiveOwner指向该线程。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值