Java并发(十):读写锁ReentrantReadWriteLock
先做总结:
1、为什么用读写锁 ReentrantReadWriteLock?
重入锁ReentrantLock是排他锁,在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。
读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会被阻塞。
2、读写锁实现原理:
(1)每个ReentrantReadWriteLock对象都对应着读锁和写锁两个锁。
(2)ReentrantReadWriteLock通过其属性sync(继承了AQS),一个对象实现了读写两个锁。
(3)sync.state(int)分为高 16 位和低16位,高16位用于共享模式ReadLock,低16位用于独占模式WriteLock
(4)获取写锁标志:
1.sync.state的低16位(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1) 最多2^16-1次重入
2.sync.exclusiveOwnerThread == Thread.currentThread()
(5)获取读锁标志:
1.state的高16位(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1) 最多2^16-1次重入
2.ThreadLocalHoldCounter readHolds; // 记录线程持有的读锁数量(ThreadLocalHoldCounter extends ThreadLocal)
readHolds.threadLocals - Map<ThreadLocal, HoldCounter>
HoldCounter - count tid
(关于ThreadLocal:Java并发(二十):线程本地变量ThreadLocal)
3.sync.cachedHoldCounter 记录最后一个获取读锁的线程的读锁重入次数,用于缓存提高性能
4.sync.firstReader 第一个获取读锁的线程(并且其未释放读锁),以及它持有的读锁数量 提高性能
(6)ReentrantReadWriteLock的内部类WriteLock/ReadLock通过操作sync的属性实现的锁的操作。
一、类结构
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
// 属性
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync; // 锁 // 内部类
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class FairSync extends Sync {}
static final class NonfairSync extends Sync {}
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
}
二、读写锁实现
ReadLock 使用了共享模式,WriteLock 使用了独占模式。
ReadLock 和 WriteLock都是通过同一个Sync实例实现的。
AQS 将 state(int)分为高 16 位和低16位,高16位用于共享模式ReadLock,低16位用于独占模式WriteLock 。
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; static int sharedCount(int c) { return c >>> SHARED_SHIFT; } // 无符号补0右移16位 - 读锁
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; } // 抹掉高16位 - 写锁
WriteLock:
1.state的低16位(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1) 最多2^16-1次重入
2.exclusiveOwnerThread == Thread.currentThread()
ReadLock:
1.state的高16位(0代表没有被占用,大于0代表有线程持有当前锁(锁可以重入,每次重入都+1) 最多2^16-1次重入
2.ThreadLocalHoldCounter readHolds; // 记录线程持有的读锁数量
readHolds.threadLocals - Map<Thread, HoldCounter>
HoldCounter - count tid
三、源码分析
写锁获取:
// ReentrantReadWriteLock.WriteLock.lock()
public void lock() {
sync.acquire(1);
} // AbstractQueuedSynchronizer.acquire(int)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} /**
* ReentrantReadWriteLock.Sync.tryAcquire(int)
* 可以获取写锁的两种情况:
* 1.没有线程占用该锁(读锁和写锁都没有被占用)
* 2.当前线程已经拿到过该写锁,重入
*/
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c); // 写锁
if (c != 0) { // 有锁
if (w == 0 || current != getExclusiveOwnerThread()) // 有锁且没有写锁(即有读锁) || 其他线程占用了写锁
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT) // 重入锁上限 2^16-1
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
// 没有线程占用该锁,直接获取锁
if (writerShouldBlock() || // 如果是公平锁需要排队
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
写锁释放:
// ReentrantReadWriteLock.WriteLock.unlock()
public void unlock() {
sync.release(1);
} // AbstractQueuedSynchronizer.release(int)
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
} /**
* ReentrantReadWriteLock.Sync.tryRelease(int)
* 释放写锁:维护state和exclusiveOwnerThread
*
*/
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
读锁
abstract static class Sync extends AbstractQueuedSynchronizer {
// 这个嵌套类的实例用来记录每个线程持有的读锁数量(读锁重入)
static final class HoldCounter {
int count = 0; // 持有的读锁数
final long tid = getThreadId(Thread.currentThread()); // 线程 id
} static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
} /**
* 组合使用上面两个类,用一个 ThreadLocal 来记录线程持有的读锁数量
*/
private transient ThreadLocalHoldCounter readHolds; /**
* 用于缓存,记录最后一个获取读锁的线程的读锁重入次数
* 不管哪个线程获取到读锁后,就把这个值占为已用,这样就不用到 ThreadLocal 中查询 map 了
* 通常读锁的获取很快就会伴随着释放,在 获取->释放 读锁这段时间,如果没有其他线程获取读锁的话,此缓存就能帮助提高性能
*/
private transient HoldCounter cachedHoldCounter; /**
* 第一个获取读锁的线程(并且其未释放读锁),以及它持有的读锁数量
* 提高性能用
*/
private transient Thread firstReader = null;
private transient int firstReaderHoldCount; }
读锁获取:
// ReentrantReadWriteLock.ReadLock.lock()
public void lock() {
sync.acquireShared(1);
} // AbstractQueuedSynchronizer.acquireShared(int)
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
} /**
* ReentrantReadWriteLock.Sync.tryAcquireShared(int)
* 可以获取读锁情况:
* 1.没有线程占用该锁
* 2.只有读锁
* 3.有写锁,写锁被当前线程占用,锁降级
* 三种情况 - 只要没有其他线程占用写锁就可以获取读锁
*/
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current) // 其他线程占用写锁
return -1;
int r = sharedCount(c); // 读锁
if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { // 进入此if,表示可以拿到读锁了
if (r == 0) { // 将"第一个"获取读锁的线程记录在firstReader属性中
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else { // 1.当前线程及对应读锁次数存入cachedHoldCounter 2.当前线程及对应读锁次数存入readHolds
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();// readHolds中取当先线程的ThreadLocal(没有就创建一个)
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);// 公平锁排队非公平锁下一个是写锁/读锁重入次数上限/CAS失败 重新拿读锁
} /**
* ReentrantReadWriteLock.Sync.fullTryAcquireShared(Thread)
*/
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) { // 循环CAS拿锁
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current) // 其他线程占用写锁
return -1;
} else if (readerShouldBlock()) { // 处理读锁重入,将cachedHoldCounter设置为当前线程
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
} else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0) // 不是重入,返回-1
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
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 != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
锁降级:
持有写锁的线程,去获取读锁的过程称为锁降级
读锁释放:
// ReentrantReadWriteLock.ReadLock.unlock()
public void unlock() {
sync.releaseShared(1);
} // AbstractQueuedSynchronizer.releaseShared(int)
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
} /**
* ReentrantReadWriteLock.Sync.tryReleaseShared(int)
* 维护readHolds state
*/
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) { // 第一个获取读锁的线程
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else { // readHolds中次数-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;
}
for (;;) { // state
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
参考资料 / 相关推荐
Java 读写锁 ReentrantReadWriteLock 源码分析
【死磕Java并发】—–J.U.C之读写锁:ReentrantReadWriteLock
Java并发(十):读写锁ReentrantReadWriteLock的更多相关文章
- java并发编程-读写锁
最近项目中需要用到读写锁 读写锁适用于读操作多,写操作少的场景,假设你的程序中涉及到对一些共享资源的读和写操作,且写操作没有读操作那么频繁.在没有写操作的时候,两个线程同时读一个资源没有任何问题,所以 ...
- java并发之读写锁ReentrantReadWriteLock的使用
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...
- java并发:读写锁ReadWriteLock
在没有写操作的时候,两个线程同时读一个资源没有任何问题,允许多个线程同时读取共享资源. 但是如果有一个线程想去写这些共享资源,就不应该再有其它线程对该资源进行读或写. 简单来说,多个线程同时操作同一资 ...
- JAVA 并发编程-读写锁之模拟缓存系统(十一)
在多线程中,为了提高效率有些共享资源同意同一时候进行多个读的操作,但仅仅同意一个写的操作,比方一个文件,仅仅要其内容不变能够让多个线程同一时候读,不必做排他的锁定,排他的锁定仅仅有在写的时候须要,以保 ...
- Java并发指南10:Java 读写锁 ReentrantReadWriteLock 源码分析
Java 读写锁 ReentrantReadWriteLock 源码分析 转自:https://www.javadoop.com/post/reentrant-read-write-lock#toc5 ...
- java 可重入读写锁 ReentrantReadWriteLock 详解
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt206 读写锁 ReadWriteLock读写锁维护了一对相关的锁,一个用于只 ...
- 轻松掌握java读写锁(ReentrantReadWriteLock)的实现原理
转载:https://blog.csdn.net/yanyan19880509/article/details/52435135 前言 前面介绍了java中排它锁,共享锁的底层实现机制,本篇再进一步, ...
- [图解Java]读写锁ReentrantReadWriteLock
图解ReentrantReadWriteLock 如果之前使用过读写锁, 那么可以直接看本篇文章. 如果之前未使用过, 那么请配合我的另一篇文章一起看:[源码分析]读写锁ReentrantReadWr ...
- 读写锁ReentrantReadWriteLock:读读共享,读写互斥,写写互斥
介绍 DK1.5之后,提供了读写锁ReentrantReadWriteLock,读写锁维护了一对锁:一个读锁,一个写锁.通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升.在读多写少的情况下, ...
随机推荐
- 【洛谷 P4360】 [CEOI2004]锯木厂选址(斜率优化)
题目链接 一开始我的\(dp\)方程列错了,其实也不能说列错了,毕竟我交上去还是把暴力的分都拿到了,只是和题解的不一样,然后搞半天没搞出来去看题解,又看不懂,对不上,原来状态设置不一样自闭了. \(f ...
- jq消除网页滚动条
网页有些时候需要能滚动的效果,但是不想要滚动条,我就遇到了这样的需求.自己用jq写了一个垂直滚动条. 纯css也可以实现 .box::-webkit-scrollbar{display:none} 但 ...
- CRF原理解读
概率有向图又称为贝叶斯网络,概率无向图又称为马尔科夫网络.具体地,他们的核心差异表现在如何求 ,即怎么表示 这个的联合概率. 概率图模型的优点: 提供了一个简单的方式将概率模型的结构可视化. 通过 ...
- CentOS测网速
当发现上网速度变慢时,人们通常会先首先测试自己的电脑到网络服务提供商(通常被称为"最后一公里")的网络连接速度.在可用于测试宽带速度的网站中,Speedtest.net也许是使用最 ...
- Git远程操作详解【转】
转自:http://www.ruanyifeng.com/blog/2014/06/git_remote.html 作者: 阮一峰 日期: 2014年6月12日 Git是目前最流行的版本管理系统,学会 ...
- python基础===100盏灯的问题
闪存里有人这样提问这样: 第一轮操作所有电灯,第二轮操作第2盏,第4盏开关,以此类推,第三轮改变编号为3的倍数的电灯,第3盏,第6盏,如果原来那盏灯是亮的,就熄灭它,如果原来是灭的,就点亮它,以此类推 ...
- MGR Switch Muti-Primary to single_primary
MGR Muti-Primary 切换 single_primary 模式 原因:因为希望做ProxySQL+MGR之间Proxy层的消耗测试,需要把原有的MGR多主改为单主模式. 修改MGRgrou ...
- HDU 5936 朋友
题意为给出一棵n个节点的树,这棵树的每条边有一个权值,这个权值只可能是0或1. 在一局游戏开始时,会确定一个节点作为根. 当一方操作时,他们需要先选择一个不为根的点,满足该点到其父亲的边权为1; 然后 ...
- Python 类的名称空间和组合
一.Python类的名称空间 class Student(object): School = '北京大学' def __init__(self): pass stu1 = Student() stu1 ...
- 转载:Logistic回归原理及公式推导
转载自:AriesSurfer 原文见 http://blog.csdn.NET/acdreamers/article/details/27365941 Logistic回归为概率型非线性回归模型,是 ...