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,一经查实,立即删除!

相关文章

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;在执行更新…

rocketmq 启动mqbroker.cmd闪退

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

WPF之布局

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

Springboot @Validated和@Valid的区别 及使用

Valid是使用Hibernate validation的时候使用 Validated是只用Spring Validator校验机制使用 说明&#xff1a;java的JSR303声明了Valid这类接口&#xff0c;而Hibernate-validator对其进行了实现 Validation对Valid进行了二次封装&#xff0c;在使用上并没有区别&#xff0c…

【dp】CF17C. Balance

http://codeforces.com/problemset/problem/17/C 题目中给出一个仅含有a,b,c的字符串&#xff0c;已经两种操作每次选出任意两个相邻的字符&#xff0c;用第一个覆盖掉第二个或者反之&#xff0c;最后询问不考虑操作次数&#xff0c;最终有多少种不同的序列其中a&#xff0c;b,…

ECSHOP设置默认配送方式和默认支付方式

用过ECSHOP的站长都知道&#xff0c;首次登陆ECSHOP进行购物的时候&#xff0c;购物流程中没有“默认配送方式和默认支付方式”这个功能 即使网站上只有一种配送方式&#xff0c;它也不会默认选中这个唯一的配送方式。 当你的网站只有一种配送方式&#xff0c;或者&#xff0c;…

spring如何解决循环依赖

什么是循环依赖&#xff1f; 循环依赖其实是指两个及以上bean相互持有对方&#xff0c;最终形成闭环的过程&#xff08;一般聊循环依赖都是默认的单例bean&#xff09;&#xff0c;简单说就是A依赖B,B依赖C,C又依赖A。 下面我就借用别人的网图来解释下&#xff1a; 注意&#…

利用Frame Animation实现动画效果,代码正确,就是达不到变换效果

就是因为把第一帧图片设置成了ImageView的src资源&#xff0c;从而一直覆盖在变换效果之上&#xff0c;去掉ImageView的src属性即可解决。 要想使应用已载入便播放动画效果&#xff0c;直接将 animationDrawables.start(); 放在activity的各种回调函数中&#xff08;onCreate、…

【电信增值业务学习笔记】3 语音类增值业务

作者&#xff1a;gnuhpc 出处&#xff1a;http://www.cnblogs.com/gnuhpc/ 1.一卡多号&#xff1a;&#xff08;Single SIM Multiple Number -SSMN&#xff09; 为拥有一个SIM卡的移动用户提供多个电话号码作为副号码主叫&#xff1a;可以选择用主号码还是副号码发起呼叫被叫&a…

循环依赖源码深度解析

singletonObjects &#xff08;一级缓存&#xff09;它是我们最熟悉的朋友&#xff0c;俗称“单例池”“容器”&#xff0c;缓存创建完成单例Bean的地方。 earlySingletonObjects&#xff08;二级缓存&#xff09;映射Bean的早期引用&#xff0c;也就是说在这个Map里的Bean不是…

多线程间共享变量线程安全问题——ThreadLocal

Java并发编程中很重要的类&#xff1a;ThreadLocal 在多线程应用程序中&#xff0c;对共享变量进行读写的场景是很常见的。如果不使用一定的技术或方案&#xff0c;会引发各种线程安全的问题。常见解决线程安全的方式有synchronized、volatile等方式&#xff0c;但synchronized…

java8流式操作

简介&#xff1a;Stream 中文称为 “流”&#xff0c;通过将集合转换为这么一种叫做 “流” 的元素序列&#xff0c;通过声明性方式&#xff0c;能够对集合中的每个元素进行一系列并行或串行的流水线操作。 操作分类&#xff1a; .stream() stream()把一个源数据&#xff0c;可…

ArrayList源码阅读

private static void extracted() {ArrayList<StudentVO> arrayList new ArrayList<StudentVO>();arrayList.add(new StudentVO("张三", 23));arrayList.add(new StudentVO("李四", 24));arrayList.add(new StudentVO("王五", 24))…

常用的JS小功能整理

<a href"#" onclick "this.style.behaviorurl(#default#homepage);this.sethomepage(http://www.mingrisoft.com)" style" color:Black; font-size: 9pt; font-family: 宋体; text-decoration :none;" >设置主页</a> <a href&quo…

类的加载过程

类的加载过程 代码 public class Father{private int i test();private static int j method();static{System.out.print("(1)");}Father(){System.out.print("(2)");}{System.out.print("(3)");)public int test(){System.out.print("(…

教你如何开发一个 SpringBoot starter

从前从前&#xff0c;有个面试官问我一个 SpringBoot Starter 的开发流程&#xff0c;我说我没有写过 starter&#xff0c;然后就没有然后了&#xff0c;面试官说我技术深度不够。 我想说这东西不是很简单吗&#xff0c;如果要自己写一个出来也是分分钟的事情。至于就因为我没…

两分钟彻底让你明白Android Activity生命周期(图文)!

转&#xff1a;http://blog.csdn.net/qyf_5445/article/details/8290232 首先看一下Android api中所提供的Activity生命周期图(不明白的&#xff0c;可以看完整篇文章&#xff0c;在回头看一下这个图&#xff0c;你会明白的): Activity其实是继承了ApplicationContext这个类&am…

spring4和spring5的aop执行顺序区别?

spring4单切面 spring4多切面 spring4 spring5

jquery datepicker 点击日期控件不会自动更新input的值

页面代码&#xff1a;<link href"http://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" rel"stylesheet" type"text/css"/> <link href"/static/css/main.css" rel"stylesheet" type"text/css"/…