AQS之ReentrantReadWriteLock精讲分析上篇 (5)

否则,将设置当前线程对应的HoldCounter对象的值。

4.释放读锁源码分析 4.1释放锁的时候调用ReadLock的unlock方法 public void unlock() { sync.releaseShared(1); } 4.2sync.releaseShared(1)调用的是AQS中的 releaseShared方法 public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } 4.3tryReleaseShared方法的具体实现是在具体的子类中 protected final boolean tryReleaseShared(int unused) { // 获取当前线程 Thread current = Thread.currentThread(); // 当前线程是否是第一个读线程 if (firstReader == current) { // 如果读线程占用的资源为1那么将firstReader设置成null if (firstReaderHoldCount == 1) firstReader = null; // 如果不是那么就减一 else firstReaderHoldCount--; } else { // 如果当前线程不是第一个读线程 // 获取缓存计数器 HoldCounter rh = cachedHoldCounter; // 计数器为空或者计数器的tid不为当前正在运行的线程的tid if (rh == null || rh.tid != getThreadId(current)) // 获取当前线程对应的计数器 rh = readHolds.get(); // 获取计数器中count的值 int count = rh.count; if (count <= 1) { // 移除 readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } // 如果不小于等于1 减少资源占用 --rh.count; } for (;;) { // 自旋 // 获取状态 int c = getState(); // 计算状态 int nextc = c - SHARED_UNIT; // 设置状态 if (compareAndSetState(c, nextc)) return nextc == 0; } }

如果tryReleaseShared(arg)方法返回true那么执行doReleaseShared()方法,前文已经讲过该方法了。

5.释放读锁流程图

AQS之ReentrantReadWriteLock精讲分析上篇

流程解析:

首先判断当前线程是否为第一个读线程firstReader,若是,则判断第一个读线程占有的资源数firstReaderHoldCount是否为1,若是,则设置第一个读线程firstReader为空,否则,将第一个读线程占有的资源数firstReaderHoldCount减1;

若当前线程不是第一个读线程,那么首先会获取缓存计数器,若计数器为空或者tid不等于当前线程的tid值,则获取当前线程的计数器,如果计数器的计数count小于等于1,则移除当前线程对应的计数器,如果计数器的计数count小于等于0,则抛出异常,之后再减少计数即可。

哪种情况,都会进入自选操作,该循环可以确保成功设置状态state

6.注意 6.1 HoldCounter的作用

在读锁的获取、释放过程中,总是会有一个对象存在着,同时该对象在获取线程获取读锁是+1,释放读锁时-1,该对象就是HoldCounter

6.2 HoldCounter的原理

要明白HoldCounter就要先明白读锁。前面提过读锁的内在实现机制就是共享锁,对于共享锁它更加像一个计数器的概念。一次共享锁操作就相当于一次计数器的操作,获取共享锁计数器+1,释放共享锁计数器-1。只有当线程获取共享锁后才能对共享锁进行释放、重入操作。所以HoldCounter的作用就是当前线程持有共享锁的数量,这个数量必须要与线程绑定在一起,否则操作其他线程锁就会抛出异常。

6.3 读锁部分源码详解 // 表示第一个读锁线程,第一个读锁firstRead是不会加入到readHolds中 if (r == 0) {     firstReader = current;     firstReaderHoldCount = 1; // 第一个读锁线程重入 } else if (firstReader == current) {     firstReaderHoldCount++;     } else { // 如果当前线程不是第一个读线程 // 获取缓存计数器     HoldCounter rh = cachedHoldCounter;    // 计数器为空或者计数器的tid不为当前正在运行的线程的tid     if (rh == null || rh.tid != current.getId())  // 获取当前线程对应的计数器           cachedHoldCounter = rh = readHolds.get();     else if (rh.count == 0) // 加入到readHolds中         readHolds.set(rh);  //计数+1     rh.count++; }

这里为什么要搞一个firstRead、firstReaderHoldCount呢?而不是直接使用else那段代码?
这是为了一个效率问题,firstReader是不会放入到readHolds中的,如果读锁仅有一个的情况下就会避免查找readHolds。我们先看firstReader、firstReaderHoldCount的定义:

private transient Thread firstReader = null; private transient int firstReaderHoldCount;

这两个变量比较简单,一个表示线程,一个是firstReader的计数。
HoldCounter的定义:(文章中第二次提到该代码)

HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程

static final class HoldCounter { int count = 0; final long tid = Thread.currentThread().getId(); }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wppjjx.html