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的并发包中 ...
随机推荐
- spting-security入门
spting-security入门 11-
- sh_05_超市买苹果
sh_05_超市买苹果 # 1. 定义苹果的单价 price = 8.5 # 2. 挑选苹果 weight = 7.5 # 3. 计算付款金额 money = weight * price # 4. ...
- 安装memcached和elasticsearch服务并systemctl管理
[root@izbp18dv3a3metugyd02qxz bin]# rpm -qa | grep memcache [root@izbp18dv3a3metugyd02qxz bin]# yum ...
- SpringCloud 教程 (三)高可用的服务注册中心
一.准备工作 Eureka can be made even more resilient and available by running multiple instances and asking ...
- 学习日记17,、、通过反射获取model实体属性display的值
本来是想到网上直接找个用的,但是找的一些都不是我想要的,然后就参考自己摸索写了一个 这里的UserModel是我自己定义的一个实体类,代码就不用放出来了 var t = typeof(UserMode ...
- 170905-MyBatis中的关系映射
===关系映射=== 参考文档复习:1对1,1对多,多对多 1.映射(多)对一.(一)对一的关联关系 1).使用列的别名 ①.若不关联数据表,则可以得到关联对象的id属性 ②.若还希望得到关联对象的其 ...
- Guava 已经学习的代码整理
Guava 已经学习的代码整理 Guava 依赖: compile group: 'com.google.guava', name: 'guava', version: '18.0' 以下是我自己在开 ...
- vux组件样式大合集
1.Actionsheet 2.Alert 3.badge 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. ...
- java 中创建线程有哪几种方式?
Java中创建线程主要有三种方式: 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...
- vue 实现,子组件向父组件 传递数据
首先理清组件之间的关系 组件与组件之间,还存在着不同的关系.父子关系与兄弟关系(不是父子的都暂称为兄弟吧). 父子组件 父子关系即是组件 A 在它的模板中使用了组件 B,那么组件 A 就是父组件,组件 ...