java读写锁源码分析(ReentrantReadWriteLock)
读锁的调用,最终委派给其内部类 Sync extends AbstractQueuedSynchronizer
/**
* 获取读锁,如果写锁不是由其他线程持有,则获取并立即返回;
* 如果写锁被其他线程持有,阻塞,直到读锁被获得。
*/
public void lock() {
sync.acquireShared(1);
}
/**
* 以共享模式获取对象,忽略中断。通过至少先调用一次 tryAcquireShared(int) 来实现此方法,并在成功时返回。
* 否则将线程加入队列,在阻塞和运行之间切换,重复调用tryAcquireShared直到成功
*( possibly repeatedly blocking and unblocking, invoking tryAcquireShared until success)
*/
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
tryAcquireShared方法的逻辑:
整体思路如下,具体实现细节,请参考下面的源码分析 一 写锁被占用的情况 1 写锁被其他线程占用,获取失败 2 写锁被自己占用,表示“锁降级”,进入【循环cas取读锁】流程
二 写锁没有被占用的情况
1 根据读锁获取策略判断是否阻塞(readerShouldBlock方法) (1) 公平锁策略:如果当前线程不是同步队列中的第一个节点,则阻塞 (2) 非公平锁策略:为了防止写线程饥饿,如果同步队列中的第一个节点是写线程,则阻塞当前线程。 2 如果需要阻塞,分以下2种情况: (1)当前线程是读锁重入,则不需要阻塞,进入【循环cas取读锁】流程 (2)当前线程不是读锁重入,则获取失败
3 如果不需要阻塞,进入【循环cas取读锁】流程
三 【循环cas取读锁】
1 tryAcquireShared方法先进行了一次cas取锁操作,如果获取失败,则调用fullTryAcquireShared方法,循环获取。 2 什么是cas?请参考CAS
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread();
//c是锁状态为:高位16位表示共享锁的数量,低位16位表示独占锁的数量
int c = getState();
//exclusiveCount(c) 取低16位的值,也就是写锁状态位:不等于0表示写锁被占用
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
//写锁被其他线程占用,获取读锁失败
return -1; //取高16位的值,读锁状态位
int r = sharedCount(c);
//readerShouldBlock()根据读锁获取策略,返回是否阻塞当前读锁获取操作。后面会详细说明此方法
if (!readerShouldBlock() &&
r < MAX_COUNT &&
//cas修改高16位的读锁状态,即获取读锁
compareAndSetState(c, c + SHARED_UNIT)) { //首次获取读锁
if (r == 0) {
//缓存首次获取读锁的线程,及其读锁重入次数
firstReader = current;
firstReaderHoldCount = 1; } else if (firstReader == current) {
firstReaderHoldCount++; } else {
//cachedHoldCounter是最后获取锁的线程的读锁重入次数
HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current))
//readHolds是缓存了当前线程的读锁重入次数的ThreadLocal
//当前线程自然是最后获取锁的线程,故将当前线程的holdCounter赋给cachedHoldCounter
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
//缓存当前线程的holdCounter
//fullTryAcquireShared()方法中,
//获取读锁失败的线程会执行:readHolds.remove(),故此时需要重新设置
readHolds.set(rh); rh.count++;
}
return 1;
}
//首次获取读锁失败后,重试获取
return fullTryAcquireShared(current);
}
/**
* Full version of acquire for reads, that handles CAS misses
* and reentrant reads not dealt with in tryAcquireShared.
*/
final int fullTryAcquireShared(Thread current) {
//rh表示当前线程的锁计数器
HoldCounter rh = null;
for (;;) {
int c = getState(); //写锁被占用
if (exclusiveCount(c) != 0) {
//如果其他线程占用,读锁获取失败。如果当前线程占用,表示“锁降级”。
if (getExclusiveOwnerThread() != current)
return -1; } else if (readerShouldBlock()) {
//重入锁不需要阻塞。 // 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 != current.getId()) {
rh = readHolds.get();
if (rh.count == 0)
//线程阻塞之前,清空readHolds
readHolds.remove();
}
}
if (rh.count == 0)
//当前线程的锁计数器为0,非重入锁,需要阻塞。
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded"); //cas设置读锁状态位
if (compareAndSetState(c, c + SHARED_UNIT)) { if (sharedCount(c) == 0) {
//缓存首次获取读锁的线程,以及锁计数器
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
else if (rh.count == 0)
//锁计数器放入ThreadLocal
readHolds.set(rh); rh.count++;
//此时rh就是最后获取读锁的线程的锁计数器
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
/**
* 非公平锁的读锁获取策略
*/
final boolean readerShouldBlock() {
//为了防止写线程饥饿
//如果同步队列中的第一个线程是以独占模式获取锁(写锁),
//那么当前获取读锁的线程需要阻塞,让队列中的第一个线程先执行
return apparentlyFirstQueuedIsExclusive();
} /**
* 公平锁的读锁获取策略
*/
final boolean readerShouldBlock() {
//如果当前线程不是同步队列头结点的next节点(head.next)
//则阻塞当前线程
return hasQueuedPredecessors();
}
java读写锁源码分析(ReentrantReadWriteLock)的更多相关文章
- java并发锁ReentrantReadWriteLock读写锁源码分析
1.ReentrantReadWriterLock 基础 所谓读写锁,是对访问资源共享锁和排斥锁,一般的重入性语义为如果对资源加了写锁,其他线程无法再获得写锁与读锁,但是持有写锁的线程,可以对资源加读 ...
- 多线程高并发编程(4) -- ReentrantReadWriteLock读写锁源码分析
背景: ReentrantReadWriteLock把锁进行了细化,分为了写锁和读锁,即独占锁和共享锁.独占锁即当前所有线程只有一个可以成功获取到锁对资源进行修改操作,共享锁是可以一起对资源信息进行查 ...
- 细说并发5:Java 阻塞队列源码分析(下)
上一篇 细说并发4:Java 阻塞队列源码分析(上) 我们了解了 ArrayBlockingQueue, LinkedBlockingQueue 和 PriorityBlockingQueue,这篇文 ...
- Java split方法源码分析
Java split方法源码分析 public String[] split(CharSequence input [, int limit]) { int index = 0; // 指针 bool ...
- 【JAVA】ThreadLocal源码分析
ThreadLocal内部是用一张哈希表来存储: static class ThreadLocalMap { static class Entry extends WeakReference<T ...
- 【Java】HashMap源码分析——常用方法详解
上一篇介绍了HashMap的基本概念,这一篇着重介绍HasHMap中的一些常用方法:put()get()**resize()** 首先介绍resize()这个方法,在我看来这是HashMap中一个非常 ...
- 【Java】HashMap源码分析——基本概念
在JDK1.8后,对HashMap源码进行了更改,引入了红黑树.在这之前,HashMap实际上就是就是数组+链表的结构,由于HashMap是一张哈希表,其会产生哈希冲突,为了解决哈希冲突,HashMa ...
- Java并发包源码分析
并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互性将大大改善.现代的PC都有多个CPU或一个CPU中有多个 ...
- Java - "JUC" Semaphore源码分析
Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例 Semaphore简介 Semaphore是一个计数信号量,它的本质是一个"共享锁". 信号量维护了 ...
随机推荐
- 用Python复习离散数学(二)
这次复习的是计数问题,立刻走起吧! 1.乘法原理 如果一项工作需要t步完成的,第一步有n1种不同的选择,第二步有n2种不同的选择,……,第t步有nt中不同的选择,那么完成这项工作所有可能的选择种数为: ...
- 大数据时代,我们为什么使用hadoop
大数据时代,我们为什么使用hadoop 我们先来看看大数据时代, 什么叫大数据,“大”,说的并不仅是数据的“多”!不能用数据到了多少TB ,多少PB 来说. 对于大数据,可以用四个词来表示:大量,多样 ...
- TCP为什么是个可靠的协议
一直以来,我们都被告知TCP是可靠的.但为什么是可靠的,很多人都会说“三次握手.四次挥手”.然后我们就进入一个误区:TCP可靠是因为它在建立链路时进行了“多次”地确认.然后又有人问,“多次确认就可靠了 ...
- nginx开启后主机无法访问虚拟机的nginx解决方案
如果IP可以通的话 一般是防火墙引起 方法1.cat /etc/sysconfig/iptables # Generated by iptables-save v1. :: *filter :INPU ...
- Python基础-输入输出(IO)
Python基础-输入输出(IO) 程序会有输入和输出,输入可以从标准输入或是从一个文件读入数据,程序的输出可以以一种友好可读的方式(human-readable)打印出来,或是写进一个文件,而标准输 ...
- in和exists哪个效率高本人测试证明
in和exists哪个效率高本人测试证明 SQLSERVR语句 in和exists哪个效率高自己测试本人测试证明 最近很多人讨论in和exists哪个效率高,今天就自己测试一下 我使用的是客户的数据库 ...
- [置顶] SQL注入安全分析
(一) 应用环境列表 网络互联设备操作系统 序号 操作系统名称 设备名称 脆弱性 1 IOS_路由器_内部_1 route1 2 IOS_路由器_VPN_1 路由器_VPN_1 3 IOS ...
- 最近学习了下BI(商业智能)做报表
最近公司购买了Style intelligence 出的BI报表工具,接触新的东西不是很容易上手,这个东西是别的项目组用的,我们项目组由于进度比较快就让我先到他们项目组帮他们,为了使用这个东西,他们已 ...
- [Oracle] CPU/PSU补丁安装教程
什么是CPU/PSU Oracle CPU的全称是Critical Patch Update, Oracle对于其产品每个季度发行一次安全补丁包,通常是为了修复产品中的安全隐患. Oracle PSU ...
- linux 网络编程:客户端与服务器通过TCP协议相互通信 + UDP
1.TCP编程的客户端一般步骤: 1.创建一个socket,用函数socket(): 2.设置socket属性,用函数setsockopt():* 可选: 3.绑定IP地址.端口等信息到socket上 ...