ReentrantReadWriteLock实现原理
在java并发包java.util.concurrent中,除了重入锁ReentrantLock外,读写锁ReentrantReadWriteLock也很常用。在实际开发场景中,在使用共享资源时,可能读操作远远多于写操作。这种情况下,如果对这部分共享资源能够让多个线程读的时候不受阻塞,仅仅在写的时候保证安全性,这样效率会得到显著提升。读写锁ReentrantReadWriteLock便适用于这种场景。
再描述一下进入读锁和写锁的条件。
进入读锁:
1.没有其他线程的写锁
2.有写请求且请求线程就是持有锁的线程
进入写锁:
1.没有其他线程读锁
2.没有其他线程写锁
本篇从源码方面,简要分析ReentrantReadWriteLock的实现原理,以及展示一下它的使用效果。
源码
这是ReentrantReadWriteLock维护的一对锁
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
ReentrantReadWriteLock的构造器中,同时实例化读写锁,同时与ReentrantLock相同,也有公平锁和非公平锁之分
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
写锁
获取锁
public void lock() {
sync.acquire();
}
//这里与ReentrantLock相同
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
} protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != ) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
这里解析tryAcquire()方法。
- 获取当前线程
- 获取状态
- 获取写线程数
- 若state不为0,表示锁已被持有。再判断,如果写线程数为0,则读锁被占用,返回false;如果写线程数不为0,且独占线程不是当前线程,表示写锁被其他线程占用没返回false
- 如果写锁重入数大于最大值MAX_COUNT,抛错
- 写锁重入,返回true
- state为0,根据公平锁还是非公平锁判断是否阻塞线程。不需要阻塞就CAS更新state
- 当前线程设为独占线程,获取写锁,返回true
释放锁
public void unlock() {
sync.release();
} public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != )
unparkSuccessor(h);
return true;
}
return false;
} protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == ;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
分析tryRelease()方法
- 判断持有写锁的线程是否当前线程,不是则抛错
- state减1
- 以新state计算写锁数量,如果为0,表示完全释放;
- 完全释放就设置独占线程为null
- 如果独占线程数量不是0,还是更新state,这里就表示多次重入写锁后,释放了一次
读锁
获取锁
public void lock() {
sync.acquireShared();
} public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < )
doAcquireShared(arg);
} protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != &&
getExclusiveOwnerThread() != current)
return -;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == ) {
firstReader = current;
firstReaderHoldCount = ;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == )
readHolds.set(rh);
rh.count++;
}
return ;
}
return fullTryAcquireShared(current);
}
这里分析tryAcquireShared()方法
- 获取当前线程
- 获取state
- 如果写锁数量不为0,且独占线程不是本线程,获得读锁失败。因为写锁被其他线程占用
- 获取读锁数量
- 根据公平锁或者非公平锁判断是否应该被阻塞,判断读锁数量是否小于最大值MAX_COUNT,再尝试CAS更新state
- 以上判断都通过且更新state也成功后,如果读锁为0,记录第一个读线程和此线程占用读锁数量
- 如果第一个读线程是本线程,表示此时是读锁的重入,则把此线程占用读锁数量+1
- 如果读锁数量不为0,且此线程也不是第一个读线程,则找到当前线程的计数器,并计数+1
- 如果在阻塞判断,读锁数量判断和CAS更新是否成功这部分没有通过,则进入fullTryAcquireShared()方法,逻辑与上面的获取类似,以无限循环方式保证操作成功,不赘述。
释放锁
public void unlock() {
sync.releaseShared();
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
} protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == )
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= ) {
readHolds.remove();
if (count <= )
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
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 == ;
}
}
分析tryReleaseShared()方法
- 获取当前线程
- 如果当前线程是第一个读线程,则释放firstReader或者第一个读线程的锁计数-1
- 不是就获得当前线程的计数器。根据计数选择删除此计数器或者减少计数
- 无限循环更新state
获取锁和释放锁的源码部分代码就分析放到这里,接下来用代码时间看看ReentrantReadWriteLock的使用效果测试。
public class ReadWriteLockTest {
private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static ExecutorService executorService = Executors.newCachedThreadPool();
//读操作
public static void read(){
try {
//加读锁
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " is reading " + System.currentTimeMillis());
Thread.sleep();
} catch (InterruptedException e){ }finally {
readWriteLock.readLock().unlock();
}
}
//写操作
public static void write() {
try {
//加写锁
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " is writing "+ System.currentTimeMillis());
Thread.sleep();
} catch (InterruptedException e){ }finally {
readWriteLock.writeLock().unlock();
}
} public static void main(String[] args) {
for (int i = ; i < ; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
ReadWriteLockTest.read();
}
}); }
for (int i = ; i < ; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
ReadWriteLockTest.write();
}
});
}
}
}
执行结果如下:
pool-1-thread-2 is reading 1549002279198
pool-1-thread-1 is reading 1549002279198
pool-1-thread-3 is reading 1549002279198
pool-1-thread-4 is writing 1549002280208
pool-1-thread-5 is writing 1549002281214
pool-1-thread-6 is writing 1549002282224
可以看到,thread1,2,3在读时,是同时执行。thread4,5,6在写操作是,都差不多间隔1000毫秒。
ReentrantReadWriteLock实现原理的更多相关文章
- 快进来!花几分钟看一下 ReentrantReadWriteLock 的原理!
前言 在看完 ReentrantLock 之后,在高并发场景下 ReentrantLock 已经足够使用,但是因为 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而很多应用场景都 ...
- 线程池 ThreadPoolExecutor 原理及源码笔记
前言 前面在学习 JUC 源码时,很多代码举例中都使用了线程池 ThreadPoolExecutor,并且在工作中也经常用到线程池,所以现在就一步一步看看,线程池的源码,了解其背后的核心原理. 公众号 ...
- 深入理解Java中的锁(三)
ReadWriteLock接口 读写锁维护一对关联锁,一个只用于读操作,一个只用于写操作.读锁可以由多个线程同时持有,又称共享锁.写锁同一时间只能由一个线程持有,又称互斥锁.同一时间,两把锁不能被不同 ...
- 深入理解 Java 并发锁
本文以及示例源码已归档在 javacore 一.并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock.sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方 ...
- Spring 源码阅读环境的搭建
前言 本文记录了 Spring 源码环境的搭建方式,以及踩过的那些坑!当前版本:5.3.2-SNAPSHOT. 环境准备 Git JDK master 分支需要 JDK 11 5.2.x 分支, J ...
- 作为一名双非本科毕业的Java程序员,我该如何在日益严重的内卷化中避免被裁?
前言 对一个 Java 程序员而言,并发编程能否熟练掌握是判断他是不是优秀的重要标准之一.因为并发编程在 Java 语言中最为晦涩的知识点,它涉及内存.CPU.操作系统.编程语言等多方面的基础能力,更 ...
- concrrent类下ReentrantReadWriteLock类的原理以及使用
1.ReentrantreadWriteLock 类的介绍 Lock接口下的子类存在 ReentrantLock子类,该子类是一个线程同步处理类:ReentrantLock类的介绍详见XXX: Loc ...
- ReentrantReadWriteLock原理
原文链接:https://www.jianshu.com/p/9f98299a17a5 前言 本篇适用于了解ReentrantLock或ReentrantReadWriteLock的使用,但想要进一步 ...
- 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理
(一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...
随机推荐
- WWW基本概念
1.Internet 2.Intranet 3.万维网 注:万维网不等同于因特网,它只是因特网的一项服务. 4.TCP/IP 5.HTTP 注:HTTP是运行在应用层的一项服务. 注:服务器在没有用户 ...
- WWDC2017-whats_new_in_safari_view_controller
最后更新: 2017-08-08 官方地址: https://developer.apple.com/videos/play/wwdc2017/225/ WWDC2017中,对SafariViewCo ...
- Day02 结构类型
1.结构类型是值类型 (类是引用类型) 2.结构中也可以像类一样,定义 字段 属性 方法 但是不能给字段赋初始值 3.结构的构造方法中,必须为所有的字段赋值 4.不能为结构显示定义无参数的构造 ...
- 【zabbix】zabbix 高可用架构的实现
https://www.jianshu.com/p/249d47b089b4?utm_campaign=maleskine&utm_content=note&utm_medium=se ...
- dp培训完结(8.9)
概率与期望dp 期望: 为什么下面的式子成立? 若x可以取1,2,3,则x+c可以取1+c,2+c,3+c..........x*c可以取1*c,2*c,3*c why? 举个例子(E(x+y)=E( ...
- kafka 和 rocketMQ 的数据存储
kafka 版本:1.1.1 一个分区对应一个文件夹,数据以 segment 文件存储,segment 默认 1G. 分区文件夹: segment 文件: segment 的命名规则是怎样的? kaf ...
- 阶段1 语言基础+高级_1-3-Java语言高级_06-File类与IO流_05 IO字符流_8_使用try_catch_finally处理流中的异常
变量没有初始化的赋值 变量可能赋值会失败.设置fw为null.close报错 把close也用try catch捕获异常 修改写入w盘.实际盘符没有这个 上面异常是这里打印的 继续优化代码
- rtti读取和设置属性
http://www.cnblogs.com/hnxxcxg/archive/2013/03/02/2940565.html rtti读取和设置属性 编辑器通过 Rtti 还能够调用一个类的方法, ...
- JavaScript 基础类型,数据类型
1.基础类型:undefined,null,Boolean,Number,String,Symbol Undefined类型:一个没有被赋值的变量会有个默认值undefined; Null类型:nul ...
- C# 捕获全局异常
一.在Winform程序中捕获全局异常 在winfrom中我们需要了解Application对象中的两个事件 ①Application.ThreadException 事件--当UI线程中某个异常未被 ...