ReentrantReadWriteLock源码分析(一)
此处源码分析,主要是基于读锁,非公平机制,JDK1.8。
问题:
1、ReentrantReadWriteLock是如何创建读锁与写锁?
2、读锁与写锁的区别是什么?
3、锁的重入次数与获取锁的线程数分别是用哪种方式记录的?
4、当队列中出现多个共享模式的线程节点连续排列时,那么当第一个共享模式的线程拿到锁之后,后面的共享线程节点怎么获取锁?
一、创建ReadLock。
ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
rrw.readLock().lock();
1、当fair的值为false时,非公平的方式创建锁,当fair的值为true时,公平的方式创建锁。
2、初始化readerLock与writerLock,这两个变量是ReentrantReadWriteLock的内部变量。
3、sync执行非公平的锁。
二、lock()源码分析
2.1、sync.acquireShared(1)
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
(1)tryAcquireShared的作用是当前线程获取读锁,当返回1时,表示获取成功,-1表示获取失败。
(2)doAcquireShared,表示获取失败的时候调用。将获取失败的线程加入到等待队列中,并调用LockSupport.park方法阻塞住,等待线程释放permit。
2.2、tryAcquireShared(arg)
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread();
// 获取到占有锁的线程数
int c = getState();
// 如果写锁被占领了且不是当前线程占领,那么直接返回 -1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c); // 占有共享锁的线程数
if (!readerShouldBlock() && // 如果队列的头节点的next节点是独享模式的线程节点即获取写锁的线程节点,返回true
r < MAX_COUNT && // 共享的数据不能超过65535
compareAndSetState(c, c + SHARED_UNIT)) { // cas设置state
if (r == 0) { // 线程来拿读锁,读锁和写锁没有被任何线程拥有,那么r==0
firstReader = current; //
firstReaderHoldCount = 1;
} else if (firstReader == current) { // 如果线程重复获取读锁,那么从这里开始重入
firstReaderHoldCount++;
} else { // 如果读锁被线程x占领,线程y也要来申请读锁,那么分支就走到这里了 // HoldCounter类中存储了两个属性,一个是count,用于记录线程的重入次数,一个是tid,记录当前线程的id
HoldCounter rh = cachedHoldCounter; // 线程x拥有读锁之后,线程y第一次申请的时候会走到这里
//cachedHoldCounter 是一个缓存,保存当前操作线程的上一个线程的操作结果。线程y操作完之后,就会保存线程y的信息
// 如果另外一个线程z来获取到读锁的时候,虽然rh!=null,但是rh.tid != getThreadId(current),
//那么会创建一个默认的HoldCounter,并保存到cachedHoldCounter,并且默认的count=0
if (rh == null || rh.tid != getThreadId(current))
// readHolds.get(),查看源码可以知道,在这个方法中包含了数据初始化的过程,会调用ReentrantReadWriteLock.java
// 下面的方法
/**
* public HoldCounter initialValue() {
* return new HoldCounter();
* }
*/
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0) // 这个分支也会来到,当线程释放锁,但是没有关闭,当再次调用线程时,readHolds中会存在HoldCounter,count=0
readHolds.set(rh);
rh.count++; // 计算重入的次数
}
return 1;
}
return fullTryAcquireShared(current);
}
请注意:
(1)ReentrantReadWriteLock中维持了一个类ThreadLocalHoldCounter,这个类会生成一个map,key是线程的id,value是HoldCounter对象,HoldCounter对象如下:
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
其中count就是线程的重入次数,tid就是当前线程的id。这个是与ReentrantLock区别的地方。
(2)ReentrantReadWriteLock使用32位int类型来表示占有锁的线程数,其中高16位是获取到读锁的线程数,低16位是获取到写锁的线程数,提供了计算线程数的方法。
static final int SHARED_SHIFT = 16;(1)
static final int SHARED_UNIT = (1 << SHARED_SHIFT);(2)
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;(3)
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;(4) /** Returns the number of shared holds represented in count */
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }(5)
/** Returns the number of exclusive holds represented in count */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }(6)
c + SHARED_UNIT
其中(1)是共享移动常量;(2)是共享添加的常量;(3)是最大线程数65535(也就是11111111 11111111);(4)跟(3)一样;(5)计算共享线程数,把c的值向右移16为,并且高位补0; >>> 无符号右移,高位补0;(6)计算独享的线程数,把c的值与11111111 11111111 按位与,这样其实就是取到了写锁的线程数;(7)是共享线程+1。
源码分析:
2.2.1、readerShouldBlock()
这个方法的作用是把判断当前获取读锁的线程是否需要阻塞,条件是:在等待队列中头节点的下一个节点是独享模式的线程。
// 读锁应该被阻塞
final boolean readerShouldBlock() {
return apparentlyFirstQueuedIsExclusive();
} /**
* Returns {@code true} if the apparent first queued thread, if one
* exists, is waiting in exclusive mode. If this method returns
* {@code true}, and the current thread is attempting to acquire in
* shared mode (that is, this method is invoked from {@link
* #tryAcquireShared}) then it is guaranteed that the current thread
* is not the first queued thread. Used only as a heuristic in
* ReentrantReadWriteLock.
*如果第一个入队列的线程节点存在,并且工作在独享模式下,那么返回true;
*如果这个方法返回true,并且当前线程以共享的模式获取锁,这个方法保证了它不是第一个入队列的
*(读锁与读锁都是共存的,所以不会入队,只有当队列中有独享模式的线程节点的时候,获取共享模式的线程才会加入到队列中。)
*/
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
// 头节点存在,并且存在下一个节点,下一个节点是独享模式,下一个节点的thread不是空,则返回true
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
2.2.2、fullTryAcquireShared(current)
这个方法的作用与tryAcquireShared的作用很类似。
// 进入这个方法的条件,
/**条件1:!readerShouldBlock() && // 如果第一个入队列的线程节点存在,并且工作在独享模式下,那么返回true;
* 条件2:r < MAX_COUNT && // 共享的数据不能超过65535,读锁的线程数已经超过了65535
* 条件3:compareAndSetState(c, c + SHARED_UNIT) // 两个竞争读锁的线程都运行到这里,第一个竞争成功,那么第二个就会竞争失败,返回false
* 其实这个方法分别对这三种状态进行处理
*/
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current) {
/*
* This code is in part redundant with that in
* tryAcquireShared but is simpler overall by not
* complicating tryAcquireShared with interactions between
* retries and lazily reading hold counts.
*/
HoldCounter rh = null;
for (;;) {
int c = getState();
// 如果排他锁被别的线程拿了,直接返回-1
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
// else we hold the exclusive lock; blocking here
// would cause deadlock.
} else if (readerShouldBlock()) { // 这里是对条件1的处理
// 如果队列的头的下一个节点是请求的排他锁的线程在等待,那么就返回true
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
// 如果当前线程的count==0,也就是说当前线程才进来,没有获取到锁,那么直接把它从readHolds中移除
if (rh.count == 0)
// 移除当前线程的HoldCounter
readHolds.remove();
}
}
// 移除之后,返回-1
if (rh.count == 0)
return -1;
}
}
// 这里是对条件2的处理,直接抛出错误!
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// 这里是对条件3的处理,竞争设置state,如果竞争还是失败,那么就要再循环一次,直到死循环能够跳出去
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 如果共享锁的数量为0
if (sharedCount(c) == 0) {
// 设置第一个线程为当前的线程
firstReader = current;
// 设置HoldCount =1
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
ReentrantReadWriteLock源码分析(一)的更多相关文章
- 【Java并发编程】16、ReentrantReadWriteLock源码分析
一.前言 在分析了锁框架的其他类之后,下面进入锁框架中最后一个类ReentrantReadWriteLock的分析,它表示可重入读写锁,ReentrantReadWriteLock中包含了两种锁,读锁 ...
- ReentrantReadWriteLock 源码分析
ReentrantReadWriteLock 源码分析: 1:数据结构: 成员变量: private final ReentrantReadWriteLock.ReadLock readerLock ...
- Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析
Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...
- Java显式锁学习总结之五:ReentrantReadWriteLock源码分析
概述 我们在介绍AbstractQueuedSynchronizer的时候介绍过,AQS支持独占式同步状态获取/释放.共享式同步状态获取/释放两种模式,对应的典型应用分别是ReentrantLock和 ...
- ReentrantReadWriteLock源码分析笔记
ReentrantReadWriteLock包含两把锁,一是读锁ReadLock, 此乃共享锁, 一是写锁WriteLock, 此乃排它锁. 这两把锁都是基于AQS来实现的. 下面通过源码来看看Ree ...
- ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)
前言 上一篇讲解了 AQS 的独占锁部分(参看:ReentrantLock 源码分析以及 AQS (一)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadW ...
- ReentrantReadWriteLock源码分析及理解
本文结构 读写锁简介:介绍读写锁.读写锁的特性以及类定义信息 公平策略及Sync同步器:介绍读写锁提供的公平策略以及同步器源码分析 读锁:介绍读锁的一些常用操作和读锁的加锁.解锁的源码分析 写锁:介绍 ...
- 多线程之美7一ReentrantReadWriteLock源码分析
目录 前言 在多线程环境下,为了保证线程安全, 我们通常会对共享资源加锁操作,我们常用Synchronized关键字或者ReentrantLock 来实现,这两者加锁方式都是排他锁,即同一时刻最多允许 ...
- Java并发编程笔记之读写锁 ReentrantReadWriteLock 源码分析
我们知道在解决线程安全问题上使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而实际情况下会有写少读多的场景,显然 Reentrant ...
随机推荐
- word2vec相关资源
word2vec官网:https://code.google.com/p/word2vec/ 利用中文数据跑Google开源项目word2vec:http://www.cnblogs.com/hebi ...
- 在原型设计上,UI和UX设计师有哪三个区别?
原型设计在日常的软件开发过程中是必不可少的,不管是UI还是UX设计师,很多工作都会涉及到原型设计.那么这两类设计师在设计原型的时候表现出了哪些的不同点呢?今天就让我们来讨论一下,先说说我发现的3个不同 ...
- asp.net状态保持
1.首先如果不是asp.net webform而只是一个纯粹的html页面和ashx一般处理程序的话,因为http协议的无状态,每一次的页面请求都会重新实例化一个页面对象(注意实例化页面对象其实是通过 ...
- 如何修改路由器的登录IP地址?
如何修改路由器的登录IP地址? 因为有多个路由器,为了区分不同路由器,我们可以修改它的登录IP,而且修改后,可以在连接的电脑上直观地知道所连接的是哪一台路由器 买回来的路由器,一般默认的登录地址是19 ...
- 2018.10.21 codeforces1071B. Minimum path(dp+贪心+bfs)
传送门 唉考试的时候写错了两个细节调了一个多小时根本没调出来. 下来又调了半个小时才过. 其实很简单. 我们先dpdpdp出最开始最多多少个连续的aaa. 然后对于没法继续连续下去的用贪心+bfsbf ...
- 2018.09.15 bzoj1977:次小生成树 Tree(次小生成树+树剖)
传送门 一道比较综合的好题. 由于是求严格的次小生成树. 我们需要维护一条路径上的最小值和次小值. 其中最小值和次小值不能相同. 由于不喜欢倍增我选择了用树链剖分维护. 代码: #include< ...
- 第三章 形容词(Les adjectifs)
★形容词的性(Le genre de l'adjectif ) ()一般规则是在阳性形容词后加-e: français ➞francaise法国的 content ➞c ...
- faceswap安装说明
Installing Faceswap Installing Faceswap Prerequisites Hardware Requirements Supported operating syst ...
- 被“1”和“l”给坑了
由于输入法的原因,导致小写的英文字母“L”和阿拉伯数字“1”长得非常像,在写秘钥的时候很容易把看错.由于对方发秘钥的时候给的是图片,而且不太清晰,手动输入的时候把“1”写成“l”了,对了好几遍秘钥也没 ...
- day02(继承,重写,this,super,final,抽象类)
继承 概述:日常生活中儿女总会继承长辈的遗产,java语言也是.通过继承多种实物之间形成一种关系. 特点: ①一个类只能继承一个父类 ②一个父类可以有多个子类 ③可以多层继承(a类继承b类 C类继 ...