0
点赞
收藏
分享

微信扫一扫

AQS之ReentrantReadWriteLock分析 (九)


文章目录

  • ​​1.ReentrantReadWriteLock 介绍​​
  • ​​1.1 Sync​​
  • ​​1.2 state​​
  • ​​1.3 ReadLock和WriteLock​​
  • ​​2.读写锁​​
  • ​​2.1 锁的降级​​
  • ​​3.读写锁源码分析​​
  • ​​3.1 Sync 分析​​
  • ​​3.2 加锁和解锁分析​​
  • ​​3.2.1 加锁分析​​
  • ​​3.2.2 解锁分析​​
  • ​​3.3 加锁解锁总结​​

1.ReentrantReadWriteLock 介绍

AQS之ReentrantReadWriteLock分析 (九)_AQS

ReentrantReadWriteLock即可重入读写锁,其同时应用了共享锁和排斥锁,写锁使用排斥锁,读锁使用共享锁,从而实现读读共享,读写互斥,写写互斥。

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

1.1 Sync

读写锁使用的是一个Sync同步器(使用一个对象),可以分别创建。有公平锁和非公平锁两种子类进行实现。

1.2 state

一个32位的二进制数,分成两部分,高16位用于共享锁,表示持有锁的线程数;低16位用于排斥锁,表示锁重入次数。

  • 读状态的获得(获得高16位状态):state >>> 16(无符号补0右移16位)
  • 写状态的获得(获得低16位状态):state & 0x0000ffff(将高16位全部抹去)

AQS之ReentrantReadWriteLock分析 (九)_java_02

1.3 ReadLock和WriteLock

​ReadLock​​​和​​WriteLock​​​是​​ReentrantReadWriteLock​​​的内部类,都实现了Lock接口,分别表示读锁和写锁,用老控制读和写操作,
使用中,需要通过调用​​​public ReentrantReadWriteLock.ReadLock readLock()​​​和​​public ReentrantReadWriteLock.WriteLock writeLock()​​​获取到对应的读锁和写锁,然后进行​​lock​​​和​​unlock​​进行锁的获取和释放

ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
// 读锁
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
// 写锁
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

2.读写锁

当state等于0 --> 写状态和读状态都为0,表示未获得锁
当state不等于0,当写状态等于0时 --> 读状态大于0,表示获得读锁
当state不等于0,当读状态等于0时 --> 写状态大于0,表示获得写锁
当state不等于0,当读状态和写状态都不等于0时 --> 发生锁的降级

2.1 锁的降级

锁的降级: 获取写锁 -> 获取写锁 -> 释放写锁 -> 释放读锁

AQS之ReentrantReadWriteLock分析 (九)_开发语言_03

public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
// 读锁
ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
// 写锁
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

writeLock.lock();
System.out.println("1");
readLock.lock();
System.out.println("2");
writeLock.unlock();
System.out.println("3");
readLock.unlock();
}

AQS之ReentrantReadWriteLock分析 (九)_java_04

AQS之ReentrantReadWriteLock分析 (九)_读写锁_05

这里如何使得读状态=2, 写状态=3的, 一个线程获取到写锁, 然后重入2次, 然后发生锁降级, 在释放写锁的时候, 获取到读锁, 并且重入一次。

3.读写锁源码分析

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

AQS之ReentrantReadWriteLock分析 (九)_开发语言_06

AQS之ReentrantReadWriteLock分析 (九)_开发语言_07

  • FairSync: Sync的公平锁的实现
  • NonfairSync: Sync的非公平锁的实现
  • ReadLock和WriteLock: 其实和ReentrantLock相同的结构, 只不过实现的方法不同

AQS之ReentrantReadWriteLock分析 (九)_AQS_08

AQS之ReentrantReadWriteLock分析 (九)_读写锁_09

AQS之ReentrantReadWriteLock分析 (九)_读写锁_10

AQS之ReentrantReadWriteLock分析 (九)_开发语言_11

AQS之ReentrantReadWriteLock分析 (九)_读写锁_12

AQS之ReentrantReadWriteLock分析 (九)_读锁_13

3.1 Sync 分析

AQS之ReentrantReadWriteLock分析 (九)_AQS_14

这里通过分析, 得出一个32位的二进制数,分成两部分,高16位用于共享锁,表示持有锁的线程数;低16位用于排斥锁,表示锁重入次数。

3.2 加锁和解锁分析

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

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

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

3.2.1 加锁分析

t1为写锁, t2为读锁

AQS之ReentrantReadWriteLock分析 (九)_读锁_15

此时线程t1占有锁流程与之前讲的ReentrantLock相似,不同之处时写锁状态占了state的低16位,而读锁占了state的高16位。
同ReentrantLock类似,t1同时也修改了state位的值,表示该锁已被占有。

此时出现t2竞争,执行 r.lock() ,这时进入读锁的 sync . acquireShared (1)流程
首先会进入 tryAcquireShared 流程。如果有写锁占据,那么 tryAcquireShared

AQS之ReentrantReadWriteLock分析 (九)_开发语言_16

AQS之ReentrantReadWriteLock分析 (九)_读写锁_17

返回-1表示失败, 0表示成功, 但是后继节点不会唤醒, 正数也成功, 而且后面可以唤醒节点, 读写锁返回1, 此处由于t1占据锁,所以此处t2的尝试是失败的,于是返回-1。

private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

先调用 addWaiter 添加节点,不同之处在于节点被设置为 Node . SHARED 模式(此处我们假设t2要获取的是读锁)而非 Node . EXCLUSIVE(如果尝试获取的是写锁) 模式,注意此时t2仍处于活跃状态。

调用 tryAcquireShared (1)来尝试获取锁, 没成功的话, for (;;)循环一次, 把前驱节点的waitStatus改为-1。

AQS之ReentrantReadWriteLock分析 (九)_读写锁_18

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

3.2.2 解锁分析

写锁解锁, 调用unlock() -> sync.release(1)

AQS之ReentrantReadWriteLock分析 (九)_java_19

先调用tryRelease(), 成功接下来执行唤醒流程unparkSuccessor, 让t1恢复运行

读锁解锁, unlock() -> releaseShared(1)

AQS之ReentrantReadWriteLock分析 (九)_java_20

3.3 加锁解锁总结

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

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

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


举报

相关推荐

0 条评论