JUC源码解析-ReentrantReadWriteLock

2023-12-26 01:08

本文主要是介绍JUC源码解析-ReentrantReadWriteLock,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

ReentrantLock是独占锁,只允许一个线程执行;CountDownLatch,Semaphore等是共享锁;它们分别利用了AQS的独占与共享功能;那么如果在读操作远多于写操作的情况下该如何选择?读写锁,之前的文章中介绍了如何自己实现一个读写锁,还实现了重入功能,读读,写写,读写,写读四种重入。现在来看看JUC包下的ReentrantReadWriteLock的实现。
先来大致了解下ReentrantReadWriteLock:

  • 读锁是个共享锁,写锁是个独占锁。读锁同时被多个线程获取,写锁只能被一个线程获取。读锁与写锁不能同时存在。
  • 一个线程可以多次重复获取读锁和写锁
  • 锁降级:获取写锁的线程又获取了读锁,之后释放写锁,就变成了一个读锁,这就是锁降级。
  • 锁升级:不支持升级。获取读锁的线程去获取写锁的话会造成死锁。
  • 重入数:AQS是state,前16位表示读锁的数量,后16位表示写锁的数量
  • 公平与非公平两种模式

AQS维护了一个int值,表示同步状态;对于ReentrantLock,state会在0与1之间变化,1表示已被占有后续线程入队列等待,0表示free。对于CountDownLatch,会先将state赋予个大于0的值,在该值变为0后唤醒等待队列中的线程。那么如何用它来即表示读锁又表示写锁呢?读锁我们是允许多个线程同步运行的,我们还允许重入,那么拿什么来记录每个线程读锁的重入数?

针对上面两个问题,对于同步状态status,高16位表示所有线程持有的读锁总数,低16位为一个线程的写锁总数,包括重入。

    abstract static class Sync extends AbstractQueuedSynchronizer {private static final long serialVersionUID = 6317671515068378041L;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; }static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

采用ThreadLocal来记录每个线程锁持有的读锁数目。

        static final class HoldCounter {int count = 0; // 该线程所持有的读锁数量// Use id, not reference, to avoid garbage retentionfinal long tid = getThreadId(Thread.currentThread());}static final class ThreadLocalHoldCounterextends ThreadLocal<HoldCounter> {public HoldCounter initialValue() {return new HoldCounter();}}private transient ThreadLocalHoldCounter readHolds;private transient HoldCounter cachedHoldCounter;private transient Thread firstReader = null;private transient int firstReaderHoldCount;
  • HoldCounter 静态内部类用来记录一个线程的读锁重入数,以及id;
  • ThreadLocalHoldCounter继承了ThreadLocal,实现了initialValue方法,作用是在没有set前调用get的话initialValue会被调用,HoldCounter对象会被存储到Entry里,并返回它。变量名为readHolds。因为读锁是共享的,我们利用同步状态的高16位来记录总数,用threadlocal来记录每个线程所持有的读锁数目。对于写锁来说它是独占锁,低16位代表的就是当前线程持有的写锁数目。
  • cachedHoldCounter:它是一种优化的手段,为了避免频繁的调用ThreadLocalHoldCounter的读取,更改操作,于是缓存最新一个成功获取锁的线程的HoldCounter,意思是当一个线程需要记录值的时候会先检查自己是否是cachedHoldCounter中缓存的那个线程,是的话就不用再从readHolds中获取了,减少对ThreadLocal的操作。
  • firstReader 与firstReaderHoldCount:代表首个获取读锁的线程与其所持有的读锁数,该读锁数不会存储进readHolds,这是种优化,例如获取读锁执行的线程执行然后释放下一个再获取执行释放,这样一个接一个的情况。

同ReentrantLock一样内部实现了非公平与公平两种同步器:NonfairSync &FairSync ,继承自同一同步器Sync。

    static final class NonfairSync extends Sync {private static final long serialVersionUID = -8159625535654395037L;final boolean writerShouldBlock() {return false; // writers can always barge}final boolean readerShouldBlock() {return apparentlyFirstQueuedIsExclusive();}}static final class FairSync extends Sync {private static final long serialVersionUID = -2274990926593161451L;final boolean writerShouldBlock() {return hasQueuedPredecessors();}final boolean readerShouldBlock() {return hasQueuedPredecessors();}}

只定义了writerShouldBlock & readerShouldBlock两种方法,它们作用在获取锁的过程中,决定当前线程是否该阻塞。

构造器:

    public ReentrantRe

这篇关于JUC源码解析-ReentrantReadWriteLock的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/537600

相关文章

R语言中的正则表达式深度解析

《R语言中的正则表达式深度解析》正则表达式即使用一个字符串来描述、匹配一系列某个语法规则的字符串,通过特定的字母、数字及特殊符号的灵活组合即可完成对任意字符串的匹配,:本文主要介绍R语言中正则表达... 目录前言一、正则表达式的基本概念二、正则表达式的特殊符号三、R语言中正则表达式的应用实例实例一:查找匹配

java中的Supplier接口解析

《java中的Supplier接口解析》Java8引入的Supplier接口是一个无参数函数式接口,通过get()方法延迟计算结果,它适用于按需生成场景,下面就来介绍一下如何使用,感兴趣的可以了解一下... 目录1. 接口定义与核心方法2. 典型使用场景场景1:延迟初始化(Lazy Initializati

C++ 右值引用(rvalue references)与移动语义(move semantics)深度解析

《C++右值引用(rvaluereferences)与移动语义(movesemantics)深度解析》文章主要介绍了C++右值引用和移动语义的设计动机、基本概念、实现方式以及在实际编程中的应用,... 目录一、右值引用(rvalue references)与移动语义(move semantics)设计动机1

MySQL 筛选条件放 ON后 vs 放 WHERE 后的区别解析

《MySQL筛选条件放ON后vs放WHERE后的区别解析》文章解释了在MySQL中,将筛选条件放在ON和WHERE中的区别,文章通过几个场景说明了ON和WHERE的区别,并总结了ON用于关... 今天我们来讲讲数据库筛选条件放 ON 后和放 WHERE 后的区别。ON 决定如何 "连接" 表,WHERE

Mybatis的mapper文件中#和$的区别示例解析

《Mybatis的mapper文件中#和$的区别示例解析》MyBatis的mapper文件中,#{}和${}是两种参数占位符,核心差异在于参数解析方式、SQL注入风险、适用场景,以下从底层原理、使用场... 目录MyBATis 中 mapper 文件里 #{} 与 ${} 的核心区别一、核心区别对比表二、底

Agent开发核心技术解析以及现代Agent架构设计

《Agent开发核心技术解析以及现代Agent架构设计》在人工智能领域,Agent并非一个全新的概念,但在大模型时代,它被赋予了全新的生命力,简单来说,Agent是一个能够自主感知环境、理解任务、制定... 目录一、回归本源:到底什么是Agent?二、核心链路拆解:Agent的"大脑"与"四肢"1. 规划模

MySQL字符串转数值的方法全解析

《MySQL字符串转数值的方法全解析》在MySQL开发中,字符串与数值的转换是高频操作,本文从隐式转换原理、显式转换方法、典型场景案例、风险防控四个维度系统梳理,助您精准掌握这一核心技能,需要的朋友可... 目录一、隐式转换:自动但需警惕的&ld编程quo;双刃剑”二、显式转换:三大核心方法详解三、典型场景

SQL 注入攻击(SQL Injection)原理、利用方式与防御策略深度解析

《SQL注入攻击(SQLInjection)原理、利用方式与防御策略深度解析》本文将从SQL注入的基本原理、攻击方式、常见利用手法,到企业级防御方案进行全面讲解,以帮助开发者和安全人员更系统地理解... 目录一、前言二、SQL 注入攻击的基本概念三、SQL 注入常见类型分析1. 基于错误回显的注入(Erro

C++ 多态性实战之何时使用 virtual 和 override的问题解析

《C++多态性实战之何时使用virtual和override的问题解析》在面向对象编程中,多态是一个核心概念,很多开发者在遇到override编译错误时,不清楚是否需要将基类函数声明为virt... 目录C++ 多态性实战:何时使用 virtual 和 override?引言问题场景判断是否需要多态的三个关

Springboot主配置文件解析

《Springboot主配置文件解析》SpringBoot主配置文件application.yml支持多种核心值类型,包括字符串、数字、布尔值等,文章详细介绍了Profile环境配置和加载位置,本文... 目录Profile环境配置配置文件加载位置Springboot主配置文件 application.ym