文章目录

  • 前言
  • 加锁规则
  • 同步原理
  • 源码解析
  • 实战演示

前言

前面我们学习了可重入锁ReentrantLock,可重入锁是一个排他锁,只要不是当前线程访问加锁资源都不能够进入,只能等待锁的释放。当然,这种加锁方式也有一定的适用场景。但是,如果在读多写少的情况下可重入锁ReentrantLock可能不是那么完美,比如缓存的写入和读取。今天,我们就引出可重入读写锁ReentrantReadWriteLock,其读写分离的机制,大大提升缓存场景的系统性能。

加锁规则

可重入锁ReentrantReadWriteLock内部分为读锁和写锁,读锁与写锁的加锁规则如下:

锁类型 读锁 写锁
读锁 共享 互斥
写锁 互斥 互斥

同步原理

可重入锁ReentrantReadWriteLock的同步机制依然是继承抽象同步队列AQS,其内部实现了自身的读锁与写锁的规则,覆写了AQS的一些同步标识和方法。其本质上都是使用AQS同步原理,并用AQS的阻塞队列保存阻塞线程,用AQS的STATE来表示当前锁状态,可重入本质上也是加减STATE来达到对应的目的。

源码解析

进入 package java.util.concurrent.locks 查看可重入ReentrantReadWriteLock源码:

public class ReentrantReadWriteLockimplements ReadWriteLock, java.io.Serializable {private static final long serialVersionUID = -6992448646407690164L;/** Inner class providing readlock */private final ReentrantReadWriteLock.ReadLock readerLock;/** Inner class providing writelock */private final ReentrantReadWriteLock.WriteLock writerLock;/** Performs all synchronization mechanics */final Sync sync;/*** Creates a new {@code ReentrantReadWriteLock} with* default (nonfair) ordering properties.*/public ReentrantReadWriteLock() {this(false);}/*** Creates a new {@code ReentrantReadWriteLock} with* the given fairness policy.** @param fair {@code true} if this lock should use a fair ordering policy*/public ReentrantReadWriteLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();readerLock = new ReadLock(this);writerLock = new WriteLock(this);}public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
}

如上源码所示,可重入读写锁实现了读写锁ReadWriteLock,保留读写锁的机制,增加了可重入机制。调用方可以通过传入标识实现非公平锁和公平锁来保证同步,当然可重入读写锁默认NonfairSync非公平锁同步。

继续查看源码:

//内部类继承抽象同步队列
abstract static class Sync extends AbstractQueuedSynchronizer{//加解锁省略
}//公平锁
static final class FairSync extends Sync {private static final long serialVersionUID = -2274990926593161451L;final boolean writerShouldBlock() {return hasQueuedPredecessors();}final boolean readerShouldBlock() {return hasQueuedPredecessors();}
}
//非公平锁
static final class NonfairSync extends Sync {private static final long serialVersionUID = -8159625535654395037L;final boolean writerShouldBlock() {return false; // writers can always barge}final boolean readerShouldBlock() {/* As a heuristic to avoid indefinite writer starvation,* block if the thread that momentarily appears to be head* of queue, if one exists, is a waiting writer.  This is* only a probabilistic effect since a new reader will not* block if there is a waiting writer behind other enabled* readers that have not yet drained from the queue.*/return apparentlyFirstQueuedIsExclusive();}
}//获取读锁
public static class ReadLock implements Lock, java.io.Serializable {private static final long serialVersionUID = -5992448646407690164L;private final Sync sync;/*** Constructor for use by subclasses** @param lock the outer lock object* @throws NullPointerException if the lock is null*/protected ReadLock(ReentrantReadWriteLock lock) {sync = lock.sync;}//加解锁省略
}//写锁
public static class WriteLock implements Lock, java.io.Serializable {private static final long serialVersionUID = -4992448646407690164L;private final Sync sync;/*** Constructor for use by subclasses** @param lock the outer lock object* @throws NullPointerException if the lock is null*/protected WriteLock(ReentrantReadWriteLock lock) {sync = lock.sync;}//加解锁省略
}

如上源码所示,可重入读写锁公平与非公平锁都集成内部类Sync,本质上还是继承至AQS。ReentrantReadWriteLock的读锁与写锁都是实现Lock,并覆写了其增加解锁的方法;对于锁机制的同步规则则是直接传入内部类Sync。

由此可知,可重入读写锁ReentrantReadWriteLock的加锁方法来源是Lock,同步机制来源与抽象同步队列AQS。当然对于可重入读写锁为保证读锁与写锁、写锁与写锁的互斥,覆写了AQS的同步方法,加入了满足自身规则的一些方法。

继续查看获取锁的源码:

//尝试获取共享锁
protected final int tryAcquireShared(int unused) {/** 1. 如果是写锁持有资源,其他线程直接返回失败* 2. 如果没有资源没有加写锁,则会检查资源的锁定状态,并尝试用cas修改state状态* 3. 如果第二步失败,则表示当前线程没有获取锁资格或者cas修改state失败,则该线程会重试。*/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 (r == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}return fullTryAcquireShared(current);
}/*** 重试获取共享锁,相比之前的方法更为简单的验证锁和cas修改state*/
final int fullTryAcquireShared(Thread current) {HoldCounter rh = null;for (;;) {int c = getState();if (exclusiveCount(c) != 0) {if (getExclusiveOwnerThread() != current)return -1;// else we hold the exclusive lock; blocking here// would cause deadlock.} else if (readerShouldBlock()) {// Make sure we're not acquiring read lock reentrantlyif (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)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;}}
}

如上源码所示,获取共享锁的源码主要是验证当前资源是否已经被加写锁,如果加了写锁其他线程会互斥不能获取该锁;如果没有加写锁则会进行加锁资格验证,读锁则可以共享加锁。在验证当前线程有加锁资格后,程序会用CAS修改STATE状态以保证可重入和释放锁机制的正常运行。

当然,如果当前线程之前是加了写锁,现在该线程对资源加读锁,那么此时当前写锁会进行降级为读锁。但是,反之当前线程加了读锁,然后该线程又对资源加写锁是不被支持的,因为这样会造成死锁。

实战演示

虽然可重入读写锁ReentrantReadWriteLock在实际生产中使用的场景较少,但是还是有它的一席之地的。话说有需要才会存在,可重入读写锁对我们生产的缓存操作极其重要。

以下我将就缓存的读写进行代码演示,当然也是因为引入可重入读写锁会使得缓存操作效率更高。

/*** ReentrantReadWriteLockDemo* @author senfel* @version 1.0* @date 2023/5/22 10:48*/
@Slf4j
public class ReentrantReadWriteLockDemo {private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();/*** 用内存缓存模拟*/private static final Map<String,String> cache  = new HashMap<>();/*** 获取缓存* @param key* @author senfel* @date 2023/5/22 14:44* @return java.lang.String*/public static String getValue(String key){//加读锁获取缓存try{readWriteLock.readLock().lock();String str = cache.get(key);if(StringUtils.isNotBlank(str)){return str;}}catch (Exception e){log.error("获取缓存异常:{}",e.getStackTrace());}finally {readWriteLock.readLock().unlock();}//没有获取到写入缓存,需要加写锁try {readWriteLock.writeLock().lock();//加写锁成功,再次验证是否存在缓存String str = cache.get(key);if(StringUtils.isNotBlank(str)){return str;}else{//查询数据库获取缓存,这里模拟设置一个常量标识以获取缓存str = "senfel";cache.put(key,str);return str;}}catch (Exception e){log.error("获取缓存异常:{}",e.getStackTrace());}finally {readWriteLock.writeLock().unlock();}return null;}/*** 设置缓存* @param key* @author senfel* @date 2023/5/22 14:44* @return java.lang.String*/public void setValue(String key,String value){try{readWriteLock.writeLock().lock();cache.put(key,value);}catch (Exception e){log.error("设置缓存异常:{}",e.getStackTrace());}finally {readWriteLock.writeLock().unlock();}}
}

深入分析实战可重入读写锁ReentrantReadWriteLock相关推荐

  1. java 可重入读写锁 ReentrantReadWriteLock 详解

    详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt206 读写锁 ReadWriteLock读写锁维护了一对相关的锁,一个用于只 ...

  2. Oracle java官网关于可重入读写锁ReentrantReadWriteLock的解析

    Oracle java官网关于可重入读写锁ReentrantReadWriteLock的解析 1.[原文链接](https://docs.oracle.com/javase/8/docs/api/ja ...

  3. 可重入读写锁ReentrantReadWriteLock的使用详解

    ReentrantReadWriteLock是一把可重入读写锁,这篇文章主要是从使用的角度帮你理解,希望对你有帮助. 一.性质 1.可重入 如果你了解过synchronized关键字,一定知道他的可重 ...

  4. 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁

    上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和主要的方法,显示了如何 ...

  5. 聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁

    这篇讲讲ReentrantReadWriteLock可重入读写锁,它不仅是读写锁的实现,并且支持可重入性. 聊聊高并发(十五)实现一个简单的读-写锁(共享-排他锁) 这篇讲了如何模拟一个读写锁. 可重 ...

  6. 聊聊高并发(二十九)解析java.util.concurrent各个组件(十一) 再看看ReentrantReadWriteLock可重入读-写锁...

    上一篇聊聊高并发(二十八)解析java.util.concurrent各个组件(十) 理解ReentrantReadWriteLock可重入读-写锁 讲了可重入读写锁的基本情况和基本的方法,显示了怎样 ...

  7. ReentrantReadWriteLock可重入读写锁分析

    ReentrantReadWriteLock 可重入的读写锁 什么叫可重入:就是同一个线程可以重复加锁,可以对同一个锁加多次,每次释放的时候回释放一次,直到该线程加锁次数为0,这个线程才释放锁. 什么 ...

  8. 深入浅出多线程编程实战(九)读写锁ReentrantReadWriteLock

    文章目录 前言 一.ReadWriteLock 二.ReentrantReadWriteLock 1.特性 2.使用样例 结尾 前言 在前面的文章中我们介绍过两种锁:内置锁(synchronized) ...

  9. 并发编程-19AQS同步组件之重入锁ReentrantLock、 读写锁ReentrantReadWriteLock、Condition

    文章目录 J.U.C脑图 ReentrantLock概述 ReentrantLock 常用方法 synchronized 和 ReentrantLock的比较 ReentrantLock示例 读写锁R ...

最新文章

  1. java语言编程基础_Java编程基础02——Java语言基础
  2. 使用Python配合Evernote完成每周工作安排
  3. gophp解释器_go语言环境搭建、基本使用
  4. 【NOIP2010】【Luogu1190】接水问题(给定顺序的模拟)
  5. iview组件 eslint校验出错 Parsing error: x-invalid-end-tag
  6. 以后所有经济时事的点评都不在这里
  7. 7.10 18级多校适应训练1题解
  8. 单设施重心法选址matlab编程
  9. jsp教师档案信息管理系统ssh
  10. 基于Python的BOSS直聘Python岗位数据分析
  11. 实现类似于目录的虚线填充样式
  12. android 查看路由器ip,如何查看路由器ip地址进入登录页面
  13. iPhoneX利用unc0ver来越狱iOS12
  14. pyecharts制作3d地图加柱状图
  15. 【文献解读】RNAi技术在油菜基因功能研究中的应用
  16. [回溯法] 和尚挑水问题-华为笔试
  17. SLC NAND FLASH的物理结构
  18. JVM中的对象探秘(三)- 对象的实例数据与对齐填充
  19. 【Excel / WPS表格】如何按列进行合并单元格?或者说按行进行合并单元格?
  20. 【迷人的爪哇】——抽象类和接口

热门文章

  1. 百词斩2021高频题汇总 | 备战春招,刷这30题就够了!
  2. Java批量高效压缩支持加解密支持所有压缩格式(Zip/7z/rar)
  3. excel文字显示图标集_创建自己的Excel图标集
  4. Bob,我要怎样才能成为一名 iOS 开发者
  5. 闲鱼易用高可扩的文章发布工具建设
  6. 如何制作一份完美的Facebook广告文案?让你的广告一炮而红!
  7. 出栈顺序(栈和队列)B
  8. Centos 7 安装 ifconfig命令
  9. 如何在 Windows 中快速查找文档
  10. 国内如何用chatgpt