前言

继续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. ASP.NET Aries 高级开发教程:如何写WebAPI接口

    前提: 最近,有不少同学又问到,Aries里如何提供WebAPI接口? 针对这个问题,今天给顺路写个教程,其实呢,很简单的. 方式一:直接用WebService提供接口. 用这种方式,直接添加接口就可 ...

  2. 【Luogu P1967】货车运输

    Luogu P1967 题目大意:给定一张图和q个询问,询问x节点和y节点的路径之间最小边权最大可以是多少. 可以发现对于一条边\(E(x,y)\),如果x到y有另一条路径且最小边权大于\(E(x,y ...

  3. setBounds方法,与setLayout(null)

    首先把相关容器的布局方式设为 setLayout(null); 然后调用组件的  setBounds() 方法 设置button的位置为(100,100) 长宽分别为 60,25 jButton.se ...

  4. CentOS 7 ETCD集群配置大全

    目录 前言 环境准备 安装 静态集群 配置 node01 配置文件 node02 配置文件 node03 配置文件 启动测试 查看集群状态 生成TLS证书 etcd证书创建 安装cfssl工具集 生成 ...

  5. 网络ASI

    ASIHTTPRequest  基于底层CFNetwork框架,运行效率很高 可惜作者 停止更新,有一些潜在的BUG无人去解决 老项目 ASI + SBJson 只需要用到外面的源文件 ASI还依赖于 ...

  6. 顺序队列与链式队列--C语言实现

    关于队列,因为我自己在平时使用不多,所以在这里直接将队列的两种存储方式放在一起,作为一篇随笔,这两份代码均可直接运行,亲测.注释写的应该也算比较详细了,就不过多的解释了 顺序队列 #include&l ...

  7. 【SpringSecurityOAuth2】源码分析@EnableOAuth2Sso在Spring Security OAuth2 SSO单点登录场景下的作用

    目录 一.从Spring Security OAuth2官方文档了解@EnableOAuth2Sso作用 二.源码分析@EnableOAuth2Sso作用 @EnableOAuth2Client OA ...

  8. 获取JVM转储文件的Java工具类

    在上期文章如何获取JVM堆转储文件中,介绍了几种方法获取JVM的转储文件,其中编程方法是里面唯一一个从JVM内部获取的方法.这里就不演示了其他方法获取正在运行的应用程序的堆转储,重点放在了使用编程来获 ...

  9. js中的Object.assign接受两个函数为参数的时候会发生什么?

    缘由 今天看到一段代码 return Object.assign(func1, func2); 心生疑惑,为什么 Object.assign 的参数可以是函数? 于是有了下面这一堆东西,其实都是老生常 ...

  10. window安装jboss服务器

    window安装jboss服务器 1.下载jboss服务器 地址:http://download.jboss.org/jbossas/7.1/jboss-as-7.1.1.Final/jboss-as ...