前言

继续JUC包中ReentrantReadWriteLock的学习,今天学习释放锁。

一、写锁释放锁

入口方法

 public void unlock() {
sync.release(1);
}

进入AQS追踪release方法:

 public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}

可见跟ReentrantLock调用的同一个释放锁方法,不同点就是tryRelease方法,所以此处只看此方法即可。读写锁tryRelease方法的实现在其内部类Sync中封装,如下所示:

 protected final boolean tryRelease(int releases) {
if (!isHeldExclusively()) // 判断当前线程是不是记录的独占线程,不是的话不能释放
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0; // 判断减完之后是不是0,是0的话说明当前线程都释放了,将独占线程设置为空,后面排队的可以抢占锁了
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}

跟ReentrantLock中唯一不同的地方是对于free的赋值,因为写锁的重入次数是记录在state的低16位上,所以此处要获取一下,其余的逻辑都一样。

二、读锁释放锁

 public void unlock() {
sync.releaseShared(1);
}

进入AQS追踪releaseShared方法:

 public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}

只有两个关键方法tryReleaseShared和doReleaseShared,下面分别看看它们的实现逻辑。

1、tryReleaseShared

 protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {// 如果当前线程是第一个获取锁的,因为有两个成员变量直接记录,所以只要修改这两个成员变量的值即可
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) // count为1,此时不应该把它置为0吗
firstReader = null;
else
firstReaderHoldCount--;
} else { // 不是当前线程,则要去缓存获取或者本地线程变量中获取当前线程的重入次数,给它减一,如果次数小于等于1则直接移除
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
} // 维护state的锁重入次数/获取次数记录
for (;;) {
int c = getState();
// 因为是读锁,所以一个SHARED_UNIT的值代表一个锁
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// 如果只有读锁,这里返回什么都无所谓;所以此返回值是专门为写锁准备的,后续会根据返回值去唤醒写锁,
return nextc == 0;
}
}

该方法逻辑很清晰,for循环上面的部分代码,用户将读锁当前线程记录的重入次数-1;for循环用于将AQS中维护的state中的读锁占有次数-1.返回的布尔类型用于给后续方法判断是否要唤醒写锁。后续方法即我们下一步要追踪的doReleaseShared方法。

2、doReleaseShared

 private void doReleaseShared() {
// 唤醒后续的写线程
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) { // 第一种:如果状态是-1,说明后面肯定有阻塞的任务,要去唤醒它
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
} // ws等于0说明是最后一个节点了,此时将Node的ws设置为PROPAGATE,因为后续没有节点了,所以不用唤醒
else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // 不等于head说明又有新的读锁进来了,这时要继续循环
break;
}
}

此方法用于唤醒读锁后处于挂起状态的锁,读锁后处于挂起状态的锁有两种:第一种是写锁,这很好理解,如果有读锁被占用,写锁过来的时候肯定需要挂起等读锁执行完(非相同线程),读锁执行完之后唤醒这个写锁;第二种是读锁,为什么当前执行的是读锁而后面还会有读锁被挂起呢?这就要回到系列(三)中的内容了,在系列(三)读锁加锁中我们讲过一个 apparentlyFirstQueuedIsExclusive 方法,该方法会判断队列中第一个排队的是不是写锁,如果是写锁则让当前的读锁挂起不去竞争锁,而若在队首写锁等待的过程中有多个读锁过来,则这多个读锁都会被依次挂起,这时就会出现第二种情况,即读锁执行的时候后面还有一个读锁被挂起,执行完之后需唤醒它。

此处第二种读锁唤醒读锁的场景,是在读锁加锁时触发的。在系列(三)中对这里未涉及,现在我们再回头看看。在doAcquireShared方法中,有个setHeadAndPropagate方法,在该方法中会检测下一个节点是不是读锁,如果是就调用doReleaseShared方法唤醒它。

小结

读写锁的释放锁逻辑基本就这些了,下面再做一个小结。

写锁释放逻辑跟ReentrantLock中的释放锁逻辑基本一致,因为毕竟都是独占锁。

读锁释放则复杂的多,它会先释放每个读锁线程记录的重入次数,再去减掉state中记录的加锁次数,最后还要唤醒后面挂起的线程。唤醒挂起的线程又分两种情况,一种是唤醒后面的写锁线程,另一种是唤醒读锁线程。读锁之间不互斥为什么在读锁执行时还会有读锁被挂起?是因为在读锁加锁时为防止写锁饥饿如果判断队首有写锁在等待获取锁那么后来的读锁都要挂起等待,这时就会出现多个读锁被挂起的情况。在释放读锁时唤醒的线程是写锁线程,在读锁加锁时唤醒的线程是读锁线程。

另外,对于Node.PROPAGATE这个状态一直没看出它的作用,而且查看了一下使用的地方,发现只在上面的doReleaseShared方法中用过,所以个人觉得是个可有可无的状态,不知道是为后续扩展留的状态还是有其他作用我没看出来,如果有对此有理解的园友,欢迎给答疑解惑一下,感谢!

AQS系列(四)- ReentrantReadWriteLock读写锁的释放锁的更多相关文章

  1. Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析

    目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...

  2. AQS系列(二)- ReentrantLock的释放锁

    前言 在AQS系列(一)中我们一起看了ReentrantLock加锁的过程,今天我们看释放锁,看看老Lea那冷峻的思维是如何在代码中笔走龙蛇的. 正文 追踪unlock方法: public void ...

  3. ReentrantReadWriteLock读写锁详解

    一.读写锁简介 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁.在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源:但是如果一个线 ...

  4. ReentrantReadWriteLock读写锁的使用2

    本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 这一节我们做一个缓存系统. 在读本节前 请先阅读 ReentrantReadWriteLock读写锁的使用1 第一 ...

  5. ReentrantReadWriteLock读写锁的使用

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...

  6. 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)

    Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...

  7. ReentrantReadWriteLock读写锁简单原理案例证明

    ReentrantReadWriteLock存在原因? 我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了Li ...

  8. 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

    网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...

  9. 写文章 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!

    网上关于Java中锁的话题可以说资料相当丰富,但相关内容总感觉是一大串术语的罗列,让人云里雾里,读完就忘.本文希望能为Java新人做一篇通俗易懂的整合,旨在消除对各种各样锁的术语的恐惧感,对每种锁的底 ...

随机推荐

  1. 移动端vue项目的图片上传插件

    有一移动端项目,使用的vant-ui.可是vant自带的Uploader似乎不支持一次选择多张图片上传的功能. 于是乎:在https://www.npmjs.com/查找发现找到 vue-upload ...

  2. java基础总结(1)--深入理解基本数据类型

    深入理解java数据类型 java是一种强类型语言,这就意味着必须为每一个声明变量声明一种类型.在java中,一共有8种数据类型,其中4种整型,2种浮点类型,1种字符类型和一种表示真值的boolean ...

  3. Spring(Bean)3

    bean的继承<!-- bean 的继承 作为模板来使用. 可以通过abstract="true"来指定把该bean配置为·抽象的. 通过abstract="tru ...

  4. 【Android - 自定义View】之自定义可下拉刷新或上拉加载的ListView

    首先来介绍一下这个自定义View: (1)这个自定义View的名称叫做 RefreshableListView ,继承自ListView类: (2)在这个自定义View中,用户可以设置是否支持下拉刷新 ...

  5. 英语口语考试资料Volunteers

    Being a volunteer is great!        There are lots of volunteers around us now. And they don’t do it ...

  6. matlab 降维工具 转载【https://blog.csdn.net/tarim/article/details/51253536】

    降维工具箱drtool   这个工具箱的主页如下,现在的最新版本是2013.3.21更新,版本v0.8.1b http://homepage.tudelft.nl/19j49/Matlab_Toolb ...

  7. php踩过的那些坑(5)浮点数计算

    一.前方有坑 php在使用加减乘除等运算符计算浮点数的时候,经常会出现意想不到的结果,特别是关于财务数据方面的计算,给不少工程师惹了很多的麻烦.比如今天工作终于到的一个案例: $a = 2586; $ ...

  8. 优雅解决 SpringBoot 工程中多环境下 application.properties 的维护问题

    微信号:geekoftaste, 期待与大家一起探讨! 背景 我们知道 SpringBoot 有一个全局的配置文件 application.properties, 可以把工程里用到的占位符,第三方库的 ...

  9. Javascript实现百度API

    百度地图JavaScript API是一套由JavaScript语言编写的应用程序接口,可帮助您在网站中构建功能丰富.交互性强的地图应用,支持PC端和移动端基于浏览器的地图应用开发,且支持HTML5特 ...

  10. 走近深度学习,认识MoXing:初识华为云ModelArts的王牌利器 — MoXing

    [摘要] 本文为MoXing系列文章第一篇,主要介绍什么是MoXing,MoXing API的优势以及MoXing程序的基本结构. MoXing的概念 MoXing是华为云深度学习服务提供的网络模型开 ...