03解锁源码分析-ReentrantReadWriteLock原理-AQS-并发编程(Java)

news2024/11/25 7:00:27

文章目录

    • 1 读锁解锁
      • 1.1 tryReleaseShared()
      • 1.2 doReleaseShared()
      • 1.3 unparkSuccessor()
      • 1.4 示意图
    • 2 写锁解锁
      • 2.1 tryRelease()
      • 2.2 尝试解锁成功
      • 2.3 setHeadAndPropagate()
    • 5 后记

1 读锁解锁

查看下读锁的解锁相关源代码:

public void unlock() {
    sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
	if (tryReleaseShared(arg)) {
		doReleaseShared();
		return true;
	}
	return false;
}

执行流程:调用读锁的unlock()方法

  • 调用了同步器syc的释放读锁方法releaseShared()
  • releaseShared()方法
    • 首先尝试去释放读锁
      • 成功 唤醒锁竞争队列中的结点

1.1 tryReleaseShared()

尝试释放读锁,源代码如下:

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } 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))
            // 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;
    }
}

执行流程如下:

  • 获取当前线程

  • 第一步判断当前线程是不是第一个加读锁的线程

    • 判断线程计数是否为1
      • 第一个加读锁记录置空,释放资源
    • 不为1 ,执行计数-1
  • 否则代表是其他读锁线程

    • 获取缓存的计数器
    • 如果缓存的计数器为空或者不是当前线程计数器,重新获取
    • 如果计数器计数<=1 ,移除当前线程计数器
      • 如果是<=0,抛异常
    • 计算器计数-1
  • 第二步执行for循环

    • 获取锁状态(计数)
    • cas执行锁计数-1
      • 失败,继续循环
      • 成功,判断锁计数-1是否=0即当前锁未加锁

注:

  • 如果当前有多个获取读锁的线程正在执行或者有读锁重入,那么某一个线程释放锁,锁计数不会为0,也就不会执行唤醒锁竞争队列结点的操作

1.2 doReleaseShared()

此时锁计数为0,即未加锁状态,执行唤醒操作,那么锁竞争队列如果有阻塞的结点,那么第二个一定是获取写锁的结点。

  • 写锁获取共享,前面分析过加锁原理。锁竞争队列如果有阻塞的结点,那么第一个结点是哨兵结点。

源代码如下所示:

private void doReleaseShared() {
	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 cases
				unparkSuccessor(h);
			}
			else if (ws == 0 &&
					 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
				continue;                // loop on failed CAS
		}
		if (h == head)                   // loop if head changed
			break;
	}
}

执行流程:

  • 获取头结点(哨兵结点)
  • 第一步判断头结点不为空且不等于尾结点
    • 获取头结点状态
    • 判断结点状态是否等于Node.SIGNAL(-1)
      • cas尝试把头结点状态由-1置为0
      • 失败重复下一次循环
      • 成功唤醒头结点的后继节点
    • 否则判断其他条件,这里放在注意事项里面
  • 第二步判断此时如果h还是等于头结点,结束循环

注:

  • 这个方法是继承自AbstractQueuedSynchronizer。上面第一步里面的否则判断分子,在ReentrantReadWriteLock里面不会用到,在其他同步器锁中使用。

1.3 unparkSuccessor()

这个方法也是继承自AbstractQueuedSynchronizer,在之前加锁和解锁-ReentrantLock详解-AQS-并发编程(Java)有讲解。读锁释放唤醒的是写锁结点,回到写锁park的地方继续执行,过程同ReentrantLock中一样这里不再赘述。

1.4 示意图

示意图如下1.4-1所示:在这里插入图片描述

2 写锁解锁

我们在来看下写锁解锁流程:

public void unlock() {
	sync.release(1);
}
public final boolean release(int arg) {
	if (tryRelease(arg)) {
		Node h = head;
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}
  • 写锁解锁调用unlock()方法
  • unlock()调用同步器的release()方法
  • 尝试解锁
    • 如果头结点不为空且等待状态不为0
      • 唤醒后继结点
    • 解锁成功
  • 解锁失败

2.1 tryRelease()

写锁尝试解锁,源代码如下:

protected final boolean tryRelease(int releases) {
	if (!isHeldExclusively())
		throw new IllegalMonitorStateException();
	int nextc = getState() - releases;
	boolean free = exclusiveCount(nextc) == 0;
	if (free)
		setExclusiveOwnerThread(null);
	setState(nextc);
	return free;
}

执行流程如下:

  • 判断锁持有线程是不是当前线程
    • 不是直接抛异常
  • nextc:获取锁计数-1
  • 判断读锁计数是否为0
    • 为零,当前未加锁,锁持有线程置空
  • 设置锁计数
  • 返回锁计数是否为零的判断结果
    • 为零尝试解锁成功
    • 不为零尝试解锁未成功

2.2 尝试解锁成功

tryRelease()成功之后,锁竞争队列不为空的情况下唤醒后继结点。如果第二个结点是读锁线程结点,和上面读锁解锁的情况一样这里不再赘述。这里以写锁解锁,锁竞争队列第二个、第三个为读锁线程结点的情况,分析,开始解锁初始状态如下图2.2-1所示:在这里插入图片描述

获取写锁的线程thread-0,执行unlock()方法,执行tryRelease()方法返回true,此时状态如下图所示:

在这里插入图片描述

程序继续执行,开始唤醒thread-1结点,执行unparkSuccessor()方法,该方法同上,unpark thread-1线程。

thread-1共享结点(即获取读锁时阻塞的),我们在回到读锁加锁阻塞的地方,即doAcquireShared()方法的parkAndCheckInterrupt()这里,源代码如下:

private void doAcquireShared(int arg) {
	final Node node = addWaiter(Node.SHARED);
	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 GC
					if (interrupted)
						selfInterrupt();
					failed = false;
					return;
				}
			}
			if (shouldParkAfterFailedAcquire(p, node) &&
				parkAndCheckInterrupt())
				interrupted = true;
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}
  • unpark之后,线程tread-1继续for循环,获取对应结点的前驱即头结点。
  • 执行tryAcquireShared()方法,thread-1获取读锁成功,返回1
    • tryAcquireShared()在读锁加锁有分析,不在赘述
  • setHeadAndPropagate()开始执行,会继续唤醒后继为读锁线程的结点。

注:

  • 对比读锁加锁流程,doAcquireShared()方法在尝试获取读锁失败的时候执行,再次尝试获取读锁,在写锁没有释放的情况下,也会失败,不会执行if(r>=0)的语句块;这里是写锁释放之后,未加锁情况下,尝试获取读锁会成功,然后执行该语句块。

唤醒thread-1之后,状态如下图2.2-1所示:在这里插入图片描述

2.3 setHeadAndPropagate()

设置头结点并唤醒后继的读锁线程结点,源代码如下:

private void setHeadAndPropagate(Node node, int propagate) {
	Node h = head; // Record old head for check below
	setHead(node);
	if (propagate > 0 || h == null || h.waitStatus < 0 ||
		(h = head) == null || h.waitStatus < 0) {
		Node s = node.next;
		if (s == null || s.isShared())
			doReleaseShared();
	}
}

执行流程如下:

  • 设置新的头结点
  • 条件判断,因为progagate参数为1判断条件为true
    • 获取后继结点,判断s为空或者是共享结点
      • 执行doReleaseShared(),继续唤醒后继结点
        • 之前有分析,这里不再赘述

示例执行到这里,继续唤醒thread-2结点,效果如下2.3-1所示:在这里插入图片描述

也就是说,在锁竞争队列不为空或者后继结点是读锁线程结点的情况下,会全部唤醒,这里也体现读锁共享的特征。示例到这里遇到写锁线程结点,暂时不唤醒。要等待线程执行完毕后在调用读锁解锁流程。

5 后记

如有问题,欢迎交流讨论。

❓QQ:806797785

⭐️源代码仓库地址:https://gitee.com/gaogzhen/concurrent

参考:

[1]黑马程序员.黑马程序员深入学习Java并发编程,JUC并发编程全套教程[CP/OL].2020-01-18/2022-12-12.p256~p258.

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

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

相关文章

【实时数仓】在Hbase建立维度表、保存维度数据到Hbase、保存业务数据到kafka主题

文章目录一 分流Sink之建立维度表到HBase(Phoenix)1 拼接建表语句&#xff08;1&#xff09;定义配置常量类&#xff08;2&#xff09;引入依赖&#xff08;3&#xff09;hbase-site.xml&#xff08;4&#xff09;在phoenix中执行&#xff08;5&#xff09;增加代码a TableProc…

Docker安装简单命令

一、 Docker是什么? 要了解Docker&#xff0c;首先要了解什么是容器&#xff1f; 容器是一个软件的轻量级独立可执行软件包&#xff0c;包含运行它所需的一切&#xff1a;代码&#xff0c;运行时&#xff0c;系统工具&#xff0c;系统库&#xff0c;设置。不管环境如何&…

C++(第十二篇):多态(虚函数、抽象类、虚函数表、虚表指针、多继承下的多态)

&#x1f4d2;博客主页&#xff1a;Morning_Yang丶 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4cc;本文所属专栏&#xff1a; 【C拒绝从入门到跑路】 &#x1f64f;作者水平有限&#xff0c;如果发现错误&#xff0c;敬请指正&…

13485-59-1,二肽Ala-Pro,H2N-AP-OH

Substrate for skin fibroblast prolidase.皮肤成纤维细胞prolida酶的底物。 编号: 199181中文名称: 二肽Ala-Pro英文名: Ala-ProCAS号: 13485-59-1单字母: H2N-AP-OH三字母: H2N-Ala-Pro-COOH氨基酸个数: 2分子式: C8H14N2O3平均分子量: 186.21精确分子量: 186.1等电点(PI): 6…

web前端期末大作业:红色主题中国文化网页设计与实现——基于HTML+CSS实现中国梦(20页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

电脑技巧:Win11系统新增的磁盘分区功能介绍

很多用户发现&#xff0c;新买的电脑中出厂自带的硬盘只有一个分区&#xff0c;目前只有少部分电脑机型出厂会有分多个分区的。 磁盘的单一分区往往不能满足我们的使用需要&#xff0c;Win7/8/10我们都知道可以在磁盘管理下通过压缩卷的方式来分区&#xff0c;Win11操作系统又新…

C# 程序的错误与调试

一 错误的zhongl 程序的错误通常可以分为三大类 ① 语法错误 ② 运行错误 ③ 逻辑错误 二 语法错误 1 常见的语法错误 ① 如括号不配对&#xff0c;多了或少了分号&#xff1b; ② 字母写错&#xff0c;变量未定义&#xff0c;控件命名写错&#xff1b; ③ 函数少了一个参数…

2022面试官常考的前端面试题

Ajax 它是一种异步通信的方法&#xff0c;通过直接由 js 脚本向服务器发起 http 通信&#xff0c;然后根据服务器返回的数据&#xff0c;更新网页的相应部分&#xff0c;而不用刷新整个页面的一种方法。 面试手写&#xff08;原生&#xff09;&#xff1a; //1&#xff1a;创建…

大一学生《Web编程基础》期末网页制作 HTML+CSS鲜花网页设计实例

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

NLP学习笔记(二) LSTM基本介绍

大家好&#xff0c;我是半虹&#xff0c;这篇文章来讲长短期记忆网络 (Long Short-Term Memory, LSTM) 文章行文思路如下&#xff1a; 首先通过循环神经网络引出为啥需要长短期记忆网络然后介绍长短期记忆网络的核心思想与运作方式最后通过简短的代码深入理解长短期记忆网络的…

Java面试题总结-hashcode和equals

前段时间有朋友问我&#xff1a;“你重写过 hashcode 和 equals 么&#xff0c;为什么重写 equals 时必须重写 hashCode 方法&#xff1f;” 之前的学习中有深入了解过&#xff0c;后来很久没复习了&#xff0c;淡忘许多&#xff0c;回答的时候也有很多地方卡壳&#xff0c;干脆…

【数据结构Java版】Queue队列的活用

目录 一、队列的定义 二、队列的使用 &#xff08;1&#xff09;主要方法 &#xff08;2&#xff09;实例演示 ​&#xff08;3&#xff09;注意事项 三、队列的模拟实现 四、循环队列 &#xff08;1&#xff09;循环队列定义 ​&#xff08;2&#xff09;循环队列的表…

web前端期末大作业:美食文化网页设计与实现——美食餐厅三级(HTML+CSS+JavaScript)

&#x1f468;‍&#x1f393;静态网站的编写主要是用HTML DIVCSS JS等来完成页面的排版设计&#x1f469;‍&#x1f393;,常用的网页设计软件有Dreamweaver、EditPlus、HBuilderX、VScode 、Webstorm、Animate等等&#xff0c;用的最多的还是DW&#xff0c;当然不同软件写出的…

Cambridge IGCSE Mathematics真题讲解1

考试局&#xff1a;Cambridge Assessment International Education (CAIE)考试类别&#xff1a;Cambridge International General Certificate of Secondary Education (IGCSE)考试科目&#xff1a;Mathematics考试单元&#xff1a;Paper 2 (Extended)试卷代码&#xff1a;0580…

全栈Jmeter接口测试(十四):跨线程组传递jmeter变量及cookie的处理

setUp线程组 setUp thread group&#xff1a; 一种特殊类型的线程组&#xff0c;用于在执行常规线程组之前执行一些必要的操作。 在 setup线程组下提到的线程行为与普通线程组完全相同。不同的是执行顺序--- 它会在普通线程组执行之前被触发&#xff1b; 应用场景举例&#xf…

大二Web课程设计:服装网页设计题材——HTML+CSS汉服文化带背景音乐素材带视频(12页)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

SQLAlchemy

一 概述 SQLAlchemy是 SQL工具包和对象关系映射器用于使用 数据库和 Python。它有几个不同的区域 &#xff0c;可单独使用或组合使用。其主要组成部分如下所示&#xff0c; 将组件依赖项组织成层&#xff1a; 上面两个最重要的部分 SQLAlchemy是对象关系映射器&#xff08;OR…

联盟营销是什么?和网红营销有什么区别?

之前讲过一篇关于联盟营销文章相关的&#xff0c;发现大家都很感兴趣&#xff0c;今天东哥就专门写一篇更全面的文章给大家好好介绍一下联盟营销以及它跟网红营销有什么区别吗&#xff1f; 联盟营销是什么&#xff1f; 联盟营销是一种根据营销效果付费的营销模式。商家利用第三…

Flink 运行错误 java.lang.OutOfMemoryError: Direct buffer memory

如遇到如下错误&#xff0c;表示需要调大配置项 taskmanager.memory.framework.off-heap.size 的值&#xff0c;taskmanager.memory.framework.off-heap.size 的默认值为 128MB&#xff0c;错误显示不够用需要调大。 2022-12-16 09:09:21,633 INFO [464321] [org.apache.hadoo…

西门子Siemens EDI需求分析及解决方案

西门子股份公司是一家专注于工业、基础设施、交通和医疗领域的科技公司&#xff0c;始终致力于做到订单、供应以及财务流程的安全、经济、高效&#xff0c;并努力提高自身与交易伙伴之间电子商务的互惠互利。为了提高与交易伙伴之间的数据传输效率&#xff0c;西门子Siemens ED…