ReentrantReadWriteLock源码分析

概述

ReentrantReadWriteLock维护了一对相关的锁,它们分别是共享readLock和独占writeLock。关于共享读锁和排他写锁的概念其实很好理解。所谓共享读锁就是一个线程读的时候,其它线程也可以来读(共享),但是不能来写。排他写锁是指一个线程在写的时候,其它线程不能来写或读(排他)。除了这个特点之外,ReentrantReadWriteLock还有一个特点就是可重入的。它和ReentrantLock一样都是支持Condition的。而且ReentrantReadWerite还支持锁降级,即允许将写锁降级为读锁。

简单使用

最最基础的用法如下:

 ReentrantReadWriteLock lock=new ReentrantReadWriteLock();public void read(){lock.readLock().lock();//需要加读锁的操作lock.readLock().unlock();}public void write(){lock.writeLock().lock();//需要加写锁的操作lock.writeLock().unlock();}

ReentrantReadWriteLock无非就是这几种情况,读读共享,写写互斥,读写互斥,写读互斥。

下面我们就以这个最基础的用法,来分析一下其内部的原理

源码分析

继承体系

共享读锁的实现原理分析#

lock方法#

  1. 首先进入调用具体的实现
  2.     public void lock() {sync.acquireShared(1);}
    
  3. 然后调用了这个方法

public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }

其中int tryAcquireShared(int unused)的具体实现如下:

 protected final int tryAcquireShared(int unused) {/** Walkthrough:* 1. If write lock held by another thread, fail.* 2. Otherwise, this thread is eligible for*    lock wrt state, so ask if it should block*    because of queue policy. If not, try*    to grant by CASing state and updating count.*    Note that step does not check for reentrant*    acquires, which is postponed to full version*    to avoid having to check hold count in*    the more typical non-reentrant case.* 3. If step 2 fails either because thread*    apparently not eligible or CAS fails or count*    saturated, chain to version with full retry loop.*/Thread current = Thread.currentThread();int c = getState();//持有写锁的线程可以获取读锁,如果获取锁的线程不是当前线程,则返回-1if (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和firstReaderHoldCountfirstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {//如果当前线程是首次获取读锁的线程firstReaderHoldCount++;} else {//更新HoldCounterHoldCounter 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);}

整个函数的工作流程如下:

  • 如果写锁已经被持有了,但是持有写锁的不是当前写出,那么就直接返回-1(体现写锁的排他性).
  • 如果在尝试获取锁是不需要阻塞等待(由锁的公平性决定),并且读锁的共享计数小于最大值,那么就直接通过CAS更新读锁数量,获取读锁。
  • 如果第二步执行失败了,那么就会调用fullTryAcquireShared(current)

fullTryAcquireShared(current)的具体实现如下:

final int fullTryAcquireShared(Thread current) {/** This code is in part redundant with that in* tryAcquireShared but is simpler overall by not* complicating tryAcquireShared with interactions between* retries and lazily reading hold counts.*/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)//如果当前线程的持有读锁数为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)) { //CAS更新读锁数量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;}}}

可以看出其实int fullTryAcquireShared(Thread current)也每什么特别,它的代码和int tryAcquireShared(int unused)差不多。只不过是增加了自旋重试,和“持有读锁数的延迟读取”

  1. 我们回到void acquireShared(int arg)方法,如果tryAcquireShared(arg)获取读锁失败后,它调用的doAcquireShared(arg)又做了什么呢?
    它的具体实现如下
  2.  private void doAcquireShared(int arg) {final Node node = addWaiter(Node.SHARED); //添加一个共享模式的Node到等待队列尾部boolean failed = true;try {boolean interrupted = false; //获取前驱节点for (;;) {final Node p = node.predecessor();if (p == head) {//如果前驱节点,尝试获取资源int r = tryAcquireShared(arg);if (r >= 0) {//获取成功,更新等待队列,并唤醒下一个等待的节点setHeadAndPropagate(node, r);p.next = null; // help GCif (interrupted)selfInterrupt();failed = false;return;}}if (shouldParkAfterFailedAcquire(p, node) && //检查获取失败后是否可以阻塞parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

    其实整个获取共享读锁的源码看下来,我们可以发现,AQS框架下,获取锁一般的流程就是首先尝试去直接获取,如果获取不到了,那么尝试自旋获取,如果还是获取不到,那么就去等待队列排队,排队的时候,如果发现自己是第二个那么就再次尝试获取锁,如果还是没获取到,那么就老老实实的在等待队列中park阻塞等待了。

    我们通过源码,也可发现AQS框架下的锁,其实如果线程之间对锁的争用很低的时候,大多数时候直接就能拿到锁,几乎不需要排队,阻塞之类的,性能非常之高。

    unlock方法

  3. 第一步还是调用具体的实现、
  4.  public void unlock() {sync.releaseShared(1);}
  5. 具体的实现如下
  6.  public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {doReleaseShared();return true;}return false;}

  7. 首先来看tryReleaseShared(arg)
  8. protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();if (firstReader == current) { //如过当前线程是第一获取到读锁的线程// assert firstReaderHoldCount > 0;//直接更新线程持有数if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))rh = readHolds.get(); //获取当前线程的计数器int count = rh.count;if (count <= 1) {readHolds.remove();if (count <= 0)throw unmatchedUnlockException();}--rh.count;}for (;;) { //自旋int c = getState();int nextc = c - SHARED_UNIT;if (compareAndSetState(c, nextc)) //更新state// 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 == 0;}}

    我们从tryReleaseShared(arg)的实现中可以看出,它的主要是去更新锁计数器和state。如果state为0的话,就返回true,否则就返回false。

  9. 我们回过头看,如果tryReleaseShared(arg)返回true,即锁释放后state为0了,那么它会执行doReleaseShared();方法,它的具体实现如下:
  10. private void doReleaseShared() {/** Ensure that a release propagates, even if there are other* in-progress acquires/releases.  This proceeds in the usual* way of trying to unparkSuccessor of head if it needs* signal. But if it does not, status is set to PROPAGATE to* ensure that upon release, propagation continues.* Additionally, we must loop in case a new node is added* while we are doing this. Also, unlike other uses of* unparkSuccessor, we need to know if CAS to reset status* fails, if so rechecking.*/for (;;) {Node h = head;if (h != null && h != tail) {int ws = h.waitStatus;if (ws == Node.SIGNAL) {if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))continue;            // loop to recheck casesunparkSuccessor(h);}else if (ws == 0 &&!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))continue;                // loop on failed CAS}if (h == head)                   // loop if head changedbreak;}}

    这个方法的作用就是唤醒等待队列中线程,现在资源已经空闲了,等待的线程可以唤醒来获取锁了。

    排他写锁的实现原理分析#

    排他写锁的实现原理其实和ReentrantLock一致。我们只看几处和共享读锁不同的地方。

  11. //公平锁实现
    protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (!hasQueuedPredecessors() && //判断当前线程是否还有前节点compareAndSetState(0, acquires)) {//CAS修改state//获取锁成功,设置锁的持有线程为当前线程setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {//该线程之前已经拿到锁int nextc = c + acquires; //重入的体现if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc); //更新Statereturn true;}return false;}

    其实非公平锁的实现也差不多,只不过少了!hasQueuedPredecessors()它不会去判断当前线程是否还有前驱节点,直接就开始获取锁了。

    unlock方法也差不多我就不赘述了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/576189.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

@Autowired、@Qualifier、@Resource的区别

参考博文&#xff1a; http://www.cnblogs.com/happyyang/articles/3553687.html http://blog.csdn.net/revent/article/details/49203619 http://blog.csdn.net/ad921012/article/details/49679745 spring不但支持自己定义的Autowired注解&#xff0c;还支持几个由JSR-250…

UINavigationViewController的backBarButtonItem设置技巧

之前大家是否疑惑为什么设置了类似这样的代码 UIBarButtonItem *backButton [[UIBarButtonItem alloc] initWithTitle:"返回" …

MySQL行锁和表锁的含义及区别

今天在开发测试时候出现了锁表&#xff0c;原因是因为我在本地开启了事务&#xff0c;代码中打了断点&#xff0c;然后测试同学测试时候出现了锁表&#xff0c;我去排查了问题&#xff0c;然后找到相关的资料了解下&#xff0c; 总结&#xff1a;原因的表没有加索引&#xff0…

Thinking In Design Pattern——Query Object模式

什么是Query Object模式 Query Object的架构设计 Query Object在服务层的应用 测试 Query Object模式 Query Object&#xff1a;可以在领域服务层构造查询然后传给资源库使用&#xff0c;并使用某种查询翻译器将对象查询&#xff08;Query&#xff09;翻译成底层数据库持久化…

linux gcc编译C程序 分享

一个c语言程序从源文件到生成可执行文件&#xff0c;编译器需要共经历4个步骤&#xff1a;1) 预处理&#xff1a;把c文件中预处理命令扫描处理完毕&#xff0c;即对源代码文件中的文件包含(#include)、预编译语句(如宏定义#define等)进行分析&#xff0c;此时生成的文件仍然是可…

java Arrays.copyOfRange使用方法

使用场景&#xff1a;比如当一个文本框输入多个值作为查询条件&#xff0c;这时候当输入的值过多&#xff0c;我们需要最大支持多少个&#xff1f;这时候&#xff0c;输入超出个数的值&#xff0c;就被截取不要 不然后台处理逻辑就要飞前台返回不能查询这样的提示&#xff0c;…

WinForm 中 comboBox控件之数据绑定

http://www.cnblogs.com/peterzb/archive/2009/05/30/1491923.html 下面介绍三种对comboBox绑定的方式&#xff0c;分别是泛型中IList和Dictionary&#xff0c;还有数据集DataTable 一、IList 现在我们直接创建一个List集合&#xff0c;然后绑定 View Code IList<string>…

MySQL常用引擎有MyISAM和InnoDB区别

MySQL常用引擎有MyISAM和InnoDB&#xff0c;而InnoDB是mysql默认的引擎。MyISAM不支持行锁&#xff0c;而InnoDB支持行锁和表锁。 如何加锁&#xff1f; MyISAM在执行查询语句&#xff08;SELECT&#xff09;前&#xff0c;会自动给涉及的所有表加读锁&#xff0c;在执行更新…

java中异常与return

抽时间整理了下java中异常与return&#xff0c;以前这块总是弄混淆&#xff0c;觉得还是写下来慢慢整理比较好。由于水平有限&#xff0c;仅供参考。废话不多说&#xff0c;直接上代码。 下面是两个方法&#xff1a; 1 public static int throwReturn(){2 int ret…

rocketmq 启动mqbroker.cmd闪退

非常奇怪&#xff0c;broker启动闪退&#xff0c;我就摸索了好久&#xff0c;网上各种百度&#xff0c;最后得到正解 将c盘下这个store下的文件全部删除&#xff0c;就可以启动了 猜测是可能mq非正常关闭&#xff0c;导致&#xff0c;具体懂原理的大佬可以来评论区说说

星星计算器

星星计算器&#xff1a; [ 机锋下载 ]第一款&#xff0c;呃&#xff0c;…&#xff0c;自家学习安卓的时候产的&#xff0c;功能和第二款有些类似&#xff08;而且在细节功能方面我也做了很多努力&#xff09;&#xff0c;不过已经十分强大了&#xff0c;并且有自己的创新&…

java基础复习-(run方法和start方法区别)

1&#xff0c;run方法是Runnable接口中定义的&#xff0c;start方法是Thread类定义的。 所有实现Runnable的接口的类都需要重写run方法&#xff0c;run方法是线程默认要执行的方法&#xff0c;是绑定操作系统的&#xff0c;也是线程执行的入口。 start方法是Thread类的默认执行…

Web.py Cookbook 简体中文版 - 如何使用web.background

注意&#xff01;&#xff01; web.backgrounder已转移到web.py 3.X实验版本中&#xff0c;不再是发行版中的一部分。你可以在这里下载&#xff0c;要把它与application.py放置在同一目录下才能正运行。 介绍 web.background和web.backgrounder都是python装饰器&#xff0c;它可…

为什么wait, notify,notifyAll保存在Object类中,而不是Thread类

一个较难回答的 Java 问题&#xff0c; Java 编程语言又不是你设计的&#xff0c;你如何回答这个问题呢&#xff1f; 需要对 Java 编程的常识进行深入了解才行。 这个问题的好在它能反映面试者是否对 wait - notify 机制有没有了解, 以及他相关知识的理解是否明确。就像为什么…

Springboot集成MapperFactory(ma.glasnost.orika.MapperFactory)类属性复制

导入jar <dependency><groupId>ma.glasnost.orika</groupId><artifactId>orika-core</artifactId><version>1.5.2</version></dependency> 编写容器注入的类 package com.kingboy.springboot.config;import ma.glasnost.or…

WPF之布局

此文目的旨在让人快速了解&#xff0c;没有什么深度&#xff0c;如需深入了解布局&#xff0c;请参考msdn。 如果你要把WPF当winform使用&#xff0c;拖拖控件也无不可&#xff0c;不过建议还是不要拖的好。 本文将那些用的比较多的几个布局控件&#xff08;Grid、UniformGrid、…

@Size、@Max、@Min、@Length、注解的含义和区别

Min 验证 Number 和 String 对象是否大等于指定的值Max 验证 Number 和 String 对象是否小等于指定的值Size(min, max) 验证对象&#xff08;Array,Collection,Map,String&#xff09;长度是否在给定的范围之内Length(min, max) 验证字符串长度是否在给定的范围之内区别&#x…

C# WCF WinCE 解决方案 错误提示之:已超过传入消息(65536)的最大消息大小配额。若要增加配额,请使用相应绑定元素上的 MaxReceivedMessageSize 属性...

C# WCF WinCE 解决方案 错误提示之&#xff1a;已超过传入消息(65536)的最大消息大小配额。若要增加配额&#xff0c;请使用相应绑定元素上的 MaxReceivedMessageSize 属性 网上的解决方案&#xff1a; 出现这种错误&#xff0c;先去修改服务器端和客户端的MaxReceivedMessageS…

mybatis xml返回对象类型和接口定义类型不一致

最近在开发中发现xml定义的返回值类型xxxxMaper.xml <select id"selectPlanList" parameterType"Plan" resultMap"PlanListVo">select * from table_name</select> <resultMap type"com.demo.vo.PlanListVo" id"…

算法可视化

http://www.cs.usfca.edu/~galles/visualization/ComparisonSort.html http://jsrun.it/norahiko/oxIy转载于:https://www.cnblogs.com/hailuo/archive/2012/12/06/2805400.html