ReentrantReadWriteLock原理
原文链接:https://www.jianshu.com/p/9f98299a17a5
前言
本篇适用于了解ReentrantLock或ReentrantReadWriteLock的使用,但想要进一步了解原理的读者。见于之前的分析都是借鉴大量的JDK源码,这次以流程图的形式代替源码,希望读者能有更好的阅读体验。有兴趣了解源码的读者也可以借鉴本篇的分析成果做源码分析。
所谓** “独占” 即同一时间只能有一个线程持有锁。而 “重入” **是指该线程如果持有锁,可以在同步代码块内再次请求占有锁而不被阻塞,线程重入后将AQS内部状态state同步加1继续同步区的操作。但是要注意该线程要想移交锁的控制权必须完全释放重入锁,即将AQS的state同步更新到0为止。
ReentrantReadWriteLock出现的目的就是针对ReentrantLock独占带来的性能问题,使用ReentrantLock无论是“写/写”线程、“读/读”线程、“读/写”线程之间的工作都是互斥,同时只有一个线程能进入同步区域。然而大多实际场景是“读/读”线程间并不存在互斥关系,只有"读/写"线程或"写/写"线程间的操作需要互斥的。因此引入ReentrantReadWriteLock,它的特性是:** 一个资源可以被多个读操作访问,或者一个写操作访问,但两者不能同时进行。**从而提高读操作的吞吐量。
初识ReentrantReadWriteLock
ReentrantReadWriteLock并没有继承ReentrantLock,也并没有实现Lock接口,而是实现了ReadWriteLock接口,该接口提供readLock()方法获取读锁,writeLock()获取写锁。
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable { private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock; public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
} public interface ReadWriteLock { Lock readLock(); Lock writeLock();
}
默认构造方法为** 非公平模式 ,开发者也可以通过指定fair为true设置为 公平模式 **。
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
而公平模式和非公平模式分别由内部类FairSync和NonfairSync实现,这两个类继承自另一个内部类Sync,该Sync继承自AbstractQueuedSynchronizer(以后简称** AQS **),这里基本同ReentrantLock的内部实现一致。
abstract static class Sync extends AbstractQueuedSynchronizer {
}
static final class FairSync extends Sync {
}
static final class NonfairSync extends Sync {
}
在ReentrantLock的分析中得知,其独占性和重入性都是通过CAS操作维护AQS内部的state变量实现的。ReentrantReadWriteLock将这个int型state变量分为高16位和低16位,高16位表示当前读锁的占有量,低16位表示写锁的占有量,详见ReentrantReadWriteLock的内部类Sync :
abstract static class Sync extends AbstractQueuedSynchronizer {
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
/** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
...
}
读锁分析
读锁,锁定的是AQS的state变量的高16位,当state的高16位等于0,表示当前读锁未被占有;当state的高16位大于0,表示当前读锁可能被一个或多个线程占有,多于一个占有读锁的线程,允许重入。
读锁竞争

读锁的获取条件要满足:
- ** 当前的写锁未被占有(AQS state变量低16位为0) 或者当前线程是写锁占有的线程**
- ** readerShouldBlock()方法返回false **
- ** 当前读锁占有量小于最大值(2^16 -1) **
- ** 成功通过CAS操作将读锁占有量+1(AQS的state高16位同步加1) **
条件1使得读锁与写锁互斥,除非当前申请读操作的线程是占有写锁的线程,即实现了写锁降级为读锁。
条件2在非公平模式下执行的是NonfairSync类的readerShouldBlock()方法:
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
如果AQS的锁等待队列head节点后的节点非共享节点(等待读锁的节点),将返回true。
条件2在公平模式下执行的是FairSync类的readerShouldBlock方法:
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
只要AQS锁等待队列的头尾不为空,并且存在head后的节点并且节点的线程非当前线程,返回true。
条件3保证读锁的占有数不超过最大上限,条件4保证多线程竞争读锁时的安全性。
不满足条件申请读锁的线程会被封装为SHARED类型的线程节点插入到AQS锁等待队列的末尾,在插入队列尾后还有一次机会尝试获取读锁。如果还是失败的,下面判断如果队列前一节点是SIGNAL状态就将线程挂起。当线程唤醒后会再次尝试获取读锁,不满足条件会再次挂起,以此循环。
** 如果在线程挂起前获取读锁,下面会将当前节点设置为head节点,并将head后的SHARED类型的节点的唤醒。然后进入读锁同步区域。被唤醒的线程会继续尝试获取读锁,获取读锁成功后就继续上述步骤,这样就保证了队列中几个连续的等待读锁的线程被依次唤醒进入读锁同步区。**
读锁释放

读锁的释放过程即AQS的state高16位同步递减为0的过程,当state的高16位都为0表示读锁释放完毕,如果此时写锁状态为0(即该读锁不是写锁降级来的),唤醒head节点后下一个SIGNAL状态的节点的线程,一般为等待写锁的节点。如果读锁的占有数不为0,表示读锁未完全释放。或者写锁的占有数不为0,表示释放的读锁是写锁降级来的。
写锁分析
写锁的状态表示为AQS的state变量的低16位,当state低16位为0,表示当前写锁没有被占有,反之表示写锁被某个写线程占有(state = 1)或重入(state > 1)。
写锁竞争

写锁获取的条件需要满足:
- ** 读锁未被占用(AQS state高16位为0) ,写锁未被占用(state低16位为0)或者占用写锁的线程是当前线程**
- ** writerShouldBlock()方法返回false,即不阻塞写线程 **
- ** 当前写锁占有量小于最大值(2^16 -1),否则抛出Error("Maximum lock count exceeded") **
- ** 通过CAS竞争将写锁状态+1(将state低16位同步+1) **
条件1使得写锁与读锁互斥,ReentrantReadWriteLock并没有读锁升级的功能。
条件2的writerShouldBlock()方法在非公平模式下实现为:
final boolean writerShouldBlock() {
return false; // writers can always barge
}
即非公平模式下允许满足条件的写操作直接插队。
条件2的writerShouldBlock()方法在公平模式下实现为:
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
公平模式下同读锁一样,如果AQS的锁等待队列不为空,写操作无法插队。
条件3保证写锁占有线程的重入次数不会溢出上限,条件4保证多个写操作的线程竞争写锁的安全性。
不满足获取写锁条件的线程会封装为EXECLUSIVE型的NODE插入到AQS的锁等待队列尾部,通过acquireQueued方法进入循环,该循环内再次尝试获取写锁(因为经过上述操作,另一个锁占有线程可能释放了锁),否则通过shouldParkAfterFailedAcquire方法将前一节点设置为SIGNAL状态后将自身线程挂起。当线程被唤醒后会再次尝试获取写锁,失败则继续挂起,以此循环。或成功占有写锁则将当前Node设置为head节点,返回中断标记并进入同步代码区。** 与读操作不同的是写操作之间是互斥的,所以获取写锁后不会将下一个申请写操作的节点唤醒。**
写锁释放

写锁的释放过程即AQS的state低16位同步递减为0的过程,当state的高16位都为0表示写锁释放完毕,唤醒head节点后下一个SIGNAL状态的节点的线程。如果该写锁占有线程未释放写锁前还占用了读锁,那么写锁释放后该线程就完全转换成了读锁的持有线程。
小结
- 读锁的重入是允许多个申请读操作的线程的,而写锁同时只允许单个线程占有,该线程的写操作可以重入。
- 如果一个线程占有了写锁,在不释放写锁的情况下,它还能占有读锁,即写锁降级为读锁。
- 对于同时占有读锁和写锁的线程,如果完全释放了写锁,那么它就完全转换成了读锁,以后的写操作无法重入,在写锁未完全释放时写操作是可以重入的。
- 公平模式下无论读锁还是写锁的申请都必须按照AQS锁等待队列先进先出的顺序。非公平模式下读操作插队的条件是锁等待队列head节点后的下一个节点是SHARED型节点,写锁则无条件插队。
- 读锁不允许newConditon获取Condition接口,而写锁的newCondition接口实现方法同ReentrantLock。
ReentrantReadWriteLock原理的更多相关文章
- 【原创】读写锁ReentrantReadWriteLock原理分析(一)
Java里面真正意义的锁并不多,其实真正的实现Lock接口的类就三个,ReentrantLock和ReentrantReadWriteLock的两个内部类(ReentrantReadWriteLock ...
- Java架构系列问题合集-目录
接下来会做一个系列, 分类说明关于Java项目研发和架构工作需要了解的问题 Java语法 Java语法专题1: 类初始化的构造顺序 https://www.cnblogs.com/milton/p/1 ...
- Java多线程专题5: JUC, 锁
合集目录 Java多线程专题5: JUC, 锁 什么是可重入锁.公平锁.非公平锁.独占锁.共享锁 可重入锁 ReentrantLock A ReentrantLock is owned by the ...
- concrrent类下ReentrantReadWriteLock类的原理以及使用
1.ReentrantreadWriteLock 类的介绍 Lock接口下的子类存在 ReentrantLock子类,该子类是一个线程同步处理类:ReentrantLock类的介绍详见XXX: Loc ...
- 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理
(一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...
- ReentrantReadWriteLock实现原理
在java并发包java.util.concurrent中,除了重入锁ReentrantLock外,读写锁ReentrantReadWriteLock也很常用.在实际开发场景中,在使用共享资源时,可能 ...
- 轻松掌握java读写锁(ReentrantReadWriteLock)的实现原理
转载:https://blog.csdn.net/yanyan19880509/article/details/52435135 前言 前面介绍了java中排它锁,共享锁的底层实现机制,本篇再进一步, ...
- 快进来!花几分钟看一下 ReentrantReadWriteLock 的原理!
前言 在看完 ReentrantLock 之后,在高并发场景下 ReentrantLock 已经足够使用,但是因为 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而很多应用场景都 ...
- ReentrantReadWriteLock读写锁简单原理案例证明
ReentrantReadWriteLock存在原因? 我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了Li ...
随机推荐
- NodeJs之文件合并(某一文件的内容发生变化与之相关的内容重新合并)
首先,一个文件里面的内容是由多个文件共同组成的.例如一个文件夹包含有多文件(文件夹) 然后,当其中一个发生变化时所用与之有直接作用的文件(文件夹)都会重新组合. /*注意:该例子需要在同级目录下完成及 ...
- ORA-00600:内部错误代码,参数:[kpnxdcbk-2],[],[],[],[],[],[],[],[],[],[],[]
由于最近工作中常出现ORA-00600:内部错误代码,参数:[kpnxdcbk-2],[],[],[],[],[],[],[],[],[],[],[]这种异常!所以在这里讲一下我的处理方法. 笔者所遇 ...
- lombok与spring的恩怨
下面是lombok按照 Java Bean 的规范生成的 下面是spring mvc里jackson 需要的 xXxx问题还是顺势而为吧
- JAVA基础知识总结11(异常)
异常: 就是不正常.程序在运行时出现的不正常情况.其实就是程序中出现的问题.这个问题按照面向对象思想进行描述,并封装成了对象.因为问题的产生有产生的原因.有问题的名称.有问题的描述等多个属性信息存在. ...
- Codeforces 719E (线段树教做人系列) 线段树维护矩阵
题面简洁明了,一看就懂 做了这个题之后,才知道怎么用线段树维护递推式.递推式的递推过程可以看作两个矩阵相乘,假设矩阵A是初始值矩阵,矩阵B是变换矩阵,求第n项相当于把矩阵B乘了n - 1次. 那么我们 ...
- 算法Sedgewick第四版-第1章基础-015一stack只保留last指针
/************************************************************************* * * A generic queue, impl ...
- Luogu 1514 [NOIP2010] 引水入城
我就是过来开心一下……这道题从开坑以来已经堆积了大半年了……今天才发现广搜一直写挂…… 丢个线段覆盖的模板,设$f_{i}$表示覆盖区间[1, i]的最小代价,$g_{i, j}$表示覆盖区间[i, ...
- 红帽企业版RHEL7.1在研域工控板上,开机没有登陆窗口 -- 编写xorg.conf 简单三行解决Ubuntu分辩率不可调的问题
红帽企业版RHEL7.1在研域工控板上,开机没有登陆窗口 没有登陆窗口 的原因分析: 没有登陆窗口的原因是因为有多个屏幕在工作,其中一个就是build-in 屏幕(内置的虚拟屏幕)和外接的显示器,并且 ...
- Servlet入门第二天
1. GenericServlet: 1). 是一个 Serlvet. 是 Servlet 接口和 ServletConfig 接口的实现类. 但是一个抽象类. 其中的 service 方法为抽象方法 ...
- Makefile的使用
Makefile 使用 一.实验说明 课程说明 在先前的课程中,我们已经学习了 gcc 和 gdb 的使用.本节课程中,我们将介绍 Makefile 的使用.Makefile带来的好处就是--&quo ...