在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实现原理的更多相关文章

  1. 快进来!花几分钟看一下 ReentrantReadWriteLock 的原理!

    前言 在看完 ReentrantLock 之后,在高并发场景下 ReentrantLock 已经足够使用,但是因为 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而很多应用场景都 ...

  2. 线程池 ThreadPoolExecutor 原理及源码笔记

    前言 前面在学习 JUC 源码时,很多代码举例中都使用了线程池 ThreadPoolExecutor,并且在工作中也经常用到线程池,所以现在就一步一步看看,线程池的源码,了解其背后的核心原理. 公众号 ...

  3. 深入理解Java中的锁(三)

    ReadWriteLock接口 读写锁维护一对关联锁,一个只用于读操作,一个只用于写操作.读锁可以由多个线程同时持有,又称共享锁.写锁同一时间只能由一个线程持有,又称互斥锁.同一时间,两把锁不能被不同 ...

  4. 深入理解 Java 并发锁

    本文以及示例源码已归档在 javacore 一.并发锁简介 确保线程安全最常见的做法是利用锁机制(Lock.sychronized)来对共享数据做互斥同步,这样在同一个时刻,只有一个线程可以执行某个方 ...

  5. Spring 源码阅读环境的搭建

    前言 本文记录了 Spring 源码环境的搭建方式,以及踩过的那些坑!​当前版本:5.3.2-SNAPSHOT. 环境准备 Git JDK master 分支需要 JDK 11 5.2.x 分支, J ...

  6. 作为一名双非本科毕业的Java程序员,我该如何在日益严重的内卷化中避免被裁?

    前言 对一个 Java 程序员而言,并发编程能否熟练掌握是判断他是不是优秀的重要标准之一.因为并发编程在 Java 语言中最为晦涩的知识点,它涉及内存.CPU.操作系统.编程语言等多方面的基础能力,更 ...

  7. concrrent类下ReentrantReadWriteLock类的原理以及使用

    1.ReentrantreadWriteLock 类的介绍 Lock接口下的子类存在 ReentrantLock子类,该子类是一个线程同步处理类:ReentrantLock类的介绍详见XXX: Loc ...

  8. ReentrantReadWriteLock原理

    原文链接:https://www.jianshu.com/p/9f98299a17a5 前言 本篇适用于了解ReentrantLock或ReentrantReadWriteLock的使用,但想要进一步 ...

  9. 并发编程学习笔记(6)----公平锁和ReentrantReadWriteLock使用及原理

    (一)公平锁 1.什么是公平锁? 公平锁指的是在某个线程释放锁之后,等待的线程获取锁的策略是以请求获取锁的时间为标准的,即使先请求获取锁的线程先拿到锁. 2.在java中的实现? 在java的并发包中 ...

随机推荐

  1. JSP上传整个文件夹

    文件上传是最古老的互联网操作之一,20多年来几乎没有怎么变化,还是操作麻烦.缺乏交互.用户体验差. 一.前端代码 英国程序员Remy Sharp总结了这些新的接口 ,本文在他的基础之上,讨论在前端采用 ...

  2. 【bzoj3195】【 [Jxoi2012]奇怪的道路】另类压缩的状压dp好题

    (上不了p站我要死了) 啊啊,其实想清楚了还是挺简单的. Description 小宇从历史书上了解到一个古老的文明.这个文明在各个方面高度发达,交通方面也不例外.考古学家已经知道,这个文明在全盛时期 ...

  3. codevs 1098 均分纸牌 2002年NOIP全国联赛提高组 x

     时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold   题目描述 Description 有 N 堆纸牌,编号分别为 1,2,…, N.每堆上有若干张,但纸牌总数必 ...

  4. [CSP-S模拟测试]:炼金术士的疑惑(模拟+数学+高斯消元)

    题目传送门(内部题70) 输入格式 第一行一个正整数$n$,表示炼金术士已知的热化学方程式数量.接下来$n$行,每行一个炼金术士已知的热化学方程式.最后一行一个炼金术士想要求解的热化学方程式,末尾记为 ...

  5. vue项目使用axios发送请求让ajax请求头部携带cookie

    最近做vue项目时遇到登录权限问题,登录以后再发送的请求头部并没有携带登录后设置的cookie,导致后台无法校验其是否登录.检查发现是vue项目中使用axios发送ajax请求导致的.查看文档得知ax ...

  6. <foreach></foreach>标签

    当传入参数为数组或者集合时需要通过<foreach></foreach>标签进行遍历 1.首先在po类中定义一个集合或者数组 比如 private List<Intege ...

  7. 记一次SQL Server delete语句的优化过程

    今天测试反应问题,性能测试环境一个脚本执行了3个小时没有出结果,期间其他dba已经建立了一些索引但是没有效果. 语句: DELETE T  from License T  WHERE exists ( ...

  8. pve-备份

    一个50g的磁盘,用了13分钟 INFO: starting new backup job: vzdump 111 --node cu-pve04 --mode snapshot --compress ...

  9. 【转】python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法>

    [转]python---方法解析顺序MRO(Method Resolution Order)<以及解决类中super方法> MRO了解: 对于支持继承的编程语言来说,其方法(属性)可能定义 ...

  10. 关闭掉mysql 8和mysql5.7的密码验证插件validate_password

    在mysql文档中的一段话If you installed MySQL 5.7 using the MySQL Yum repository, MySQL SLES Repository, or RP ...