前言

继续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. Nethunter开启ssh服务

    ### 本节用来纪念我和我的Nethunter SSH服务的血泪史 刚安装好Nethunter后,手机的各种功能也是令我眼花缭乱,对手机是爱不释手,可是,手机开启ssh服务之后,电脑无法连接,这就很尴 ...

  2. Openlayers Projection导致经纬度颠倒问题

    问题: openlayers3调用TileWMS接口,实现Openlayers加载Geoserver转发的ArcGIS切片时,web墨卡托(wkid3857)没有问题,但是WGS84(wkid4326 ...

  3. iOS 抽奖轮盘效果实现思路

    临近活动,相信不少app都会加一个新的需求——抽奖不多废话,先上GIF效果图 作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要这是一个我的iOS交流群:937194184,不管你是小白还是大牛欢 ...

  4. JDK动态代理和CGLIB字节码增强

    一.JDK动态代理 Java 在 java.lang.reflect 包中有自己的代理支持,该类(Proxy.java)用于动态生成代理类,只需传入目标接口.目标接口的类加载器以及 Invocatio ...

  5. JAVA合并多个word文档根据文章标题生成目录

    此产品版本是免费版的,我也是在用免费,除了只能单次识别25张一下的word和生成pdf有限制,其他的功能都和正式版差不多. 如果你几十个文档,每个文档几页,输出出来超过25页,那没关系,依然可以使用. ...

  6. 程序员的算法课(3)-递归(recursion)算法

    版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/m0_37609579/article/de ...

  7. 制作属于自己的翻译软件(基于PyQt5+Python+实时翻译)

    目录 制作属于自己的翻译软件(基于PyQt5+Python+实时翻译) 翻译软件上传到github上. 软件截图 主要的思想 界面方面 程序方面 制作属于自己的翻译软件(基于PyQt5+Python+ ...

  8. MySQL分层和查询数据的流程

    MySQL分层 MySQL分层 主要分为:连接层,服务层,引擎层,存储层 客户端执行一条select命令的流程如下 连接器 功能: 负责跟客户端建立连接.获取权限.维持和管理连接 细节: 1.当用户登 ...

  9. cesium添加多个geojson文件并分别控制显示和隐藏

    /*获取geojson数据*/ function get_geojson(name,h,n){ let x=document.getElementById(n); if(x.className === ...

  10. Git的安装和使用教程详解

    ---恢复内容开始--- 本篇笔记聊聊Git的安装和使用教程 一.认 识 Git                                                            ...