读写锁原理解读

news2024/11/16 20:43:57

目录

回顾什么是读写锁

t1 w.lock,t2 r.lock

t3 r.lock,t4 w.lock 

t1 w.unlock 

t2 r.unlock,t3 r.unlock 

写锁上锁流程 

 写锁释放流程

 读锁上锁流程

 读锁释放流程


回顾什么是读写锁

读写锁是一对互斥锁,分为读锁和写锁。读锁和写锁互斥,让一个线程在进行读操作时,不允许其他线程的写操作,但是不影响其他线程的读操作;当一个线程在进行写操作时,不允许任何线程进行读操作或者写操作。读写锁用的是同一个 Sycn 同步器,因此等待队列、state 等也是同一个

t1 w.lock,t2 r.lock

1) t1 成功上锁,流程与 ReentrantLock 加锁相比没有特殊之处,不同是写锁状态占了 state 的低 16 位,而读锁 使用的是 state 的高 16 位

 2)t2 执行 r.lock,这时进入读锁的 sync.acquireShared(1) 流程,首先会进入 tryAcquireShared 流程。如果有写 锁占据,那么 tryAcquireShared 返回 -1 表示失败

tryAcquireShared 返回值表示

-1 表示失败

0 表示成功,但后继节点不会继续唤醒

正数表示成功,而且数值是还有几个后继节点需要唤醒,读写锁返回 1

3)这时会进入 sync.doAcquireShared(1) 流程,首先也是调用 addWaiter 添加节点,不同之处在于节点被设置为Node.SHARED 模式而非 Node.EXCLUSIVE 模式,注意此时 t2 仍处于活跃状态

 

4)t2 会看看自己的节点是不是老二,如果是,还会再次调用 tryAcquireShared(1) 来尝试获取锁

5)如果没有成功,在 doAcquireShared 内 for (;;) 循环一次,把前驱节点的 waitStatus 改为 -1,再 for (;;) 循环一 次尝试 tryAcquireShared(1) 如果还不成功,那么在 parkAndCheckInterrupt() 处 park

t3 r.lock,t4 w.lock 

这种状态下,假设又有 t3 加读锁和 t4 加写锁,这期间 t1 仍然持有锁,就变成了下面的样子

t1 w.unlock 

这时会走到写锁的 sync.release(1) 流程,调用 sync.tryRelease(1) 成功,变成下面的样子

接下来执行唤醒流程 sync.unparkSuccessor,即让老二恢复运行,这时 t2 在 doAcquireShared 内parkAndCheckInterrupt() 处恢复运行 这回再来一次 for (;;) 执行 tryAcquireShared 成功则让读锁计数加一

这时 t2 已经恢复运行,接下来 t2 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

事情还没完,在 setHeadAndPropagate 方法内还会检查下一个节点是否是 shared,如果是则调用doReleaseShared() 将 head 的状态从 -1 改为 0 并唤醒老二,这时 t3 在 doAcquireShared 内parkAndCheckInterrupt() 处恢复运行

这回再来一次 for (;;) 执行 tryAcquireShared 成功则让读锁计数加一

这时 t3 已经恢复运行,接下来 t3 调用 setHeadAndPropagate(node, 1),它原本所在节点被置为头节点

下一个节点不是 shared 了,因此不会继续唤醒 t4 所在节点

t2 r.unlock,t3 r.unlock 

 t2 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,但由于计数还不为零

t3 进入 sync.releaseShared(1) 中,调用 tryReleaseShared(1) 让计数减一,这回计数为零了,进入doReleaseShared() 将头节点从 -1 改为 0 并唤醒老二,即

之后 t4 在 acquireQueued 中 parkAndCheckInterrupt 处恢复运行,再次 for (;;) 这次自己是老二,并且没有其他 竞争,tryAcquire(1) 成功,修改头结点,流程结束

 

写锁上锁流程 

static final class NonfairSync extends Sync {
    // ... 省略无关代码
    // 外部类 WriteLock 方法, 方便阅读, 放在此处
    public void lock() {
        sync.acquire(1);
    }

         // AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquire(int arg) {
        if (
            // 尝试获得写锁失败
                !tryAcquire(arg) &&
            // 将当前线程关联到一个 Node 对象上, 模式为独占模式
            // 进入 AQS 队列阻塞
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
        ) {
            selfInterrupt();
        }
    }

    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryAcquire(int acquires) {
    // 获得低 16 位, 代表写锁的 state 计数
        Thread current = Thread.currentThread();
        int c = getState();
        int w = exclusiveCount(c);
        if (c != 0) {
            if (
            // c != 0 and w == 0 表示有读锁, 或者
                    w == 0 ||
                // 如果 exclusiveOwnerThread 不是自己
                            current != getExclusiveOwnerThread()
            ) {
            // 获得锁失败
                return false;
            }
            // 写锁计数超过低 16 位, 报异常
            if (w + exclusiveCount(acquires) > MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            // 写锁重入, 获得锁成功
            setState(c + acquires);
            return true;
        }
        if (
            // 判断写锁是否该阻塞, 或者
                writerShouldBlock() ||
                // 尝试更改计数失败
                        !compareAndSetState(c, c + acquires)
        ) {
        // 获得锁失败
            return false;
        }
        // 获得锁成功
        setExclusiveOwnerThread(current);
        return true;
    }

    // 非公平锁 writerShouldBlock 总是返回 false, 无需阻塞
    final boolean writerShouldBlock() {
        return false;
    }
}

 写锁释放流程

static final class NonfairSync extends Sync {
    // ... 省略无关代码
// WriteLock 方法, 方便阅读, 放在此处
    public void unlock() {
        sync.release(1);
    }
    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean release(int arg) {
    // 尝试释放写锁成功
        if (tryRelease(arg)) {
        // unpark AQS 中等待的线程
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryRelease(int releases) {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        int nextc = getState() - releases;
        // 因为可重入的原因, 写锁计数为 0, 才算释放成功
        boolean free = exclusiveCount(nextc) == 0;
        if (free) {
            setExclusiveOwnerThread(null);
        }
        setState(nextc);
        return free;
    }
}

 读锁上锁流程

static final class NonfairSync extends Sync {
    // ReadLock 方法, 方便阅读, 放在此处
    public void lock() {
        sync.acquireShared(1);
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final void acquireShared(int arg) {
        // tryAcquireShared 返回负数, 表示获取读锁失败
        if (tryAcquireShared(arg) < 0) {
            doAcquireShared(arg);
        }
    }

    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final int tryAcquireShared(int unused) {
        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)
        ) {
// ... 省略不重要的代码
            return 1;
        }
        return fullTryAcquireShared(current);
    }

    // 非公平锁 readerShouldBlock 看 AQS 队列中第一个节点是否是写锁
// true 则该阻塞, false 则不阻塞
    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
// 与 tryAcquireShared 功能类似, 但会不断尝试 for (;;) 获取读锁, 执行过程中无阻塞
    final int fullTryAcquireShared(Thread current) {
        HoldCounter rh = null;
        for (; ; ) {
            int c = getState();
            if (exclusiveCount(c) != 0) {
                if (getExclusiveOwnerThread() != current)
                    return -1;
            } else if (readerShouldBlock()) {
// ... 省略不重要的代码
            }
            if (sharedCount(c) == MAX_COUNT)
                throw new Error("Maximum lock count exceeded");
            if (compareAndSetState(c, c + SHARED_UNIT)) {
// ... 省略不重要的代码
                return 1;
            }
        }
    }

    // AQS 继承过来的方法, 方便阅读, 放在此处
    private void doAcquireShared(int arg) {
// 将当前线程关联到一个 Node 对象上, 模式为共享模式
        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) {
            // (一)
            // r 表示可用资源数, 在这里总是 1 允许传播
            //(唤醒 AQS 中下一个 Share 节点)
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (
// 是否在获取读锁失败时阻塞(前一个阶段 waitStatus == Node.SIGNAL)
                        shouldParkAfterFailedAcquire(p, node) &&
// park 当前线程
                                parkAndCheckInterrupt()
                ) {
                    interrupted = true;
                }
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

        // (一) AQS 继承过来的方法, 方便阅读, 放在此处
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        // 设置自己为 head
        setHead(node);
        // propagate 表示有共享资源(例如共享读锁或信号量)
        // 原 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
        // 现在 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
                (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
        // 如果是最后一个节点或者是等待共享读锁的节点
            if (s == null || s.isShared()) {
            // 进入 (二)
                doReleaseShared();
            }
        }
    }
    // (二) AQS 继承过来的方法, 方便阅读, 放在此处
    private void doReleaseShared() {
    // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
        // 如果 head.waitStatus == 0 ==> Node.PROPAGATE, 为了解决 bug, 见后面分析
        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
            // 下一个节点 unpark 如果成功获取读锁
            // 并且下下个节点还是 shared, 继续 doReleaseShared
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue; // loop on failed CAS
            }
            if (h == head) // loop if head changed
                break;
        }
    }
}

 读锁释放流程

static final class NonfairSync extends Sync {
    // ReadLock 方法, 方便阅读, 放在此处
    public void unlock() {
        sync.releaseShared(1);
    }
    // AQS 继承过来的方法, 方便阅读, 放在此处
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    // Sync 继承过来的方法, 方便阅读, 放在此处
    protected final boolean tryReleaseShared(int unused) {
        // ... 省略不重要的代码
        for (;;) {
            int c = getState();
            int nextc = c - SHARED_UNIT;
            if (compareAndSetState(c, nextc)) {
        // 读锁的计数不会影响其它获取读锁线程, 但会影响其它获取写锁线程
        // 计数为 0 才是真正释放
                return nextc == 0;
            }
        }
    }
    // AQS 继承过来的方法, 方便阅读, 放在此处
    private void doReleaseShared() {
        // 如果 head.waitStatus == Node.SIGNAL ==> 0 成功, 下一个节点 unpark
        // 如果 head.waitStatus == 0 ==> Node.PROPAGATE
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
            // 如果有其它线程也在释放读锁,那么需要将 waitStatus 先改为 0
        // 防止 unparkSuccessor 被多次执行
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue; // loop to recheck cases
                    unparkSuccessor(h);
                }
            // 如果已经是 0 了,改为 -3,用来解决传播性,见后文信号量 bug 分析
                else if (ws == 0 &&
                        !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue; // loop on failed CAS
            }
            if (h == head) // loop if head changed
                break;
        }
    }
}

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

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

相关文章

UG\NX二次开发 显示临时标记 UF_DISP_display_temporary_point

文章作者:里海 来源网站:https://blog.csdn.net/WangPaiFeiXingYuan 简介: 显示临时标记 UF_DISP_display_temporary_point,最后一个参数控制显示的类型,可通过下表1表2查询 表1: 表2: 0UF_DISP_NO_MARKER

jenkins构建异常Type org.springframework.boot.maven.RepackageMojo not present

目录 问题描述解决过程 问题描述 我公司dev环境使用的是spug构建&#xff0c;当时构建并没有出现这种问题&#xff0c;而线上使用的是jenkins部署到华为云上&#xff0c;构建线上的时候却出现了这个问题。 java.lang.TypeNotPresentException: Type org.springframework.boot…

leetcode72. 编辑距离(动态规划-java)

编辑距离 leetcode72. 编辑距离题目描述解题思路代码演示 动态规划代码演示 动态规划专题 leetcode72. 编辑距离 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/edit-distance 题目描述 给你两个单词 word1 和 word2&am…

关于DMS批量导入的注意事项

前言 当你注意了列命和数据库对应关系&#xff0c;批量后报错&#xff0c;常见的是无列名都好找问题&#xff0c;说一条不好找的 SQL解析失败:解析文件失败::读取字段定义异常&#xff0c;字段定义存在非法的空字段&#xff0c;请检查提交的Excel文件首行中的字段定义1、列名…

springboot本机启动elasticjob抛出异常HostException(ip is null)

1.使用的elasticjob版本为3.0.1 2.本机的IPV4在校验isReachable 返回false&#xff08;可能是使用无线网&#xff0c;导致ip验证问题&#xff09; 3.最后引入Groovy解决 引入包 <dependency><groupId>org.codehaus.groovy</groupId><artifactId>gr…

【CSS3系列】第九章 · 响应式布局和BFC

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

OpenCV快速生成带边缘的棋盘格

import numpy as np import cv2 as cv# 生成棋盘格 def generateChessBoard(xSize32,ySize32,w6,h6)::param xSize: 棋盘尺寸:param ySize: 棋盘尺寸:param w: 横向角点个数:param h: 纵向角点个数:return:w,hw1,h1boardnp.zeros((xSize*(w),ySize*(h),3),np.uint8)board.fill(…

【网页复习】4道大题

&#x1f38a;专栏【 前端易错合集】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【如愿】 大一同学小吉&#xff0c;欢迎并且感谢大家指出我的问题&#x1f970; 文章目录 &#x1f354;实现如图的导航栏⭐代码&#x1f384;注…

多元分类预测 | Matlab萤火虫算法(FA)优化极限学习机(ELM)的分类预测,多特征输入模型。FA-ELM分类预测模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元分类预测 | Matlab萤火虫算法(FA)优化极限学习机(ELM)的分类预测,多特征输入模型。FA-ELM分类预测模型 多特征输入单输出的二分类及多分类模型。程序内注释详细,直接替换数据就可以用。程序语言为matlab,程…

6 中断概览

目录 中断概览 STM32异常和中断介绍 STM32的异常一览 STM32的中断表一览 中断的优先级 中断的优先级分组 优先级分组 嵌套向量中断控制器(NVIC)功能 中断概览 什么是中断&#xff1f; 中断是指计算机运行过程中&#xff0c;出现某些意外情况需主机干预时&#xff0c;机器…

Acer宏碁笔记本电脑 暗影骑士AN515-54原厂Win10系统工厂模式恢复出厂OEM原装预装系统

Acer宏基笔记本电脑&#xff0c;Acer宏碁暗影骑士AN515-54原装出厂Windows10系统恢复原厂OEM系统镜像 系统自带所有驱动、Office办公软件、出厂主题壁纸LOGO、 Acer Care Center、Quick Access、 NitroSense风扇控制等预装程序 所需要工具&#xff1a;32G或以上的U盘&#xf…

Selenium教程__获取浏览器名称和版本(5)

通过学习本文内容&#xff0c;将能够轻松地获取并利用浏览器的信息&#xff0c;从而更好地适应不同的浏览器环境&#xff0c;并确保您的代码和测试脚本能够在各种浏览器中正常运行。 from selenium import webdriverdriver webdriver.Chrome() driver.maximize_window() dri…

SpringSecutiry整合thymeleaf模板

如何构建SpringSecutiry框架&#xff0c;这里就不详细赘述了&#xff0c;直接速通。 目录 thymeleaf教程&#xff08;转载&#xff09; 所需的依赖 Thymeleaf模板文件 具体的项目搭建 资源展览图 接口展示 Thymeleaf模板内容展示 thymeleaf教程&#xff08;转载&#xff09…

uAvionix开始首次FCC授权的C波段无人机数据链BVLOS飞行

2023年6月19日消息&#xff0c;uAvionix是一家为有人和无人驾驶飞机提供指挥、导航和监视技术的领先供应商&#xff0c;该公司今天宣布已获得FCC批准&#xff0c;并与FAA协调&#xff0c;在俄克拉荷马州Choctaw Nation新兴技术试验场运行其SkyLink C波段指挥和控制(C2)无线电&a…

netwox 基于 Ethernet 层构造 IP 数据包【网络工程】(保姆级图文)

目录 基于 Ethernet 层构造 IP 数据包1) 不指定选项&#xff0c;直接运行该模块&#xff0c;查看默认设置。执行命令如下&#xff1a;3) 验证构造的数据包&#xff0c;使用 Wireshark 工具捕获数据包&#xff0c;如图所示。其中&#xff0c;第 2 个数据包为构造的 IPv4 数据包。…

单元测试-sonarqube本地安装使用

sonarqube sonarqube是什么 SonarQube是一个开源的代码分析平台&#xff0c;用来持续分析和评测项目源代码的质量。通过SonarQube我们可以检测出项目中重复代码&#xff0c;潜在bug,代码规范&#xff0c;安全性漏洞等问题&#xff0c;并通过SonarQube web UI展示出来。 Sona…

ESC1+ESC4+CVE-2022–26923

CVE-2022–26923 创建机器账户并指定dnsHostName为dc的域名 certipy account create -u certhacktest.com -p Admin123456. -dc-ip 10.211.55.3 -user win -pass win123456 -dns DC.hacktest.com 用该机器账户向ADCS请求证书 certipy req -u win$hacktest.com -p win123456…

毕业设计之图书馆座位预约系统

1.系统开发环境 系统采用的集成开发环境为IDEA&#xff0c;使用JAVA语言及SPRINGBOOT框架进行开发&#xff0c;其中硬件环境和软件环境如下&#xff1a; 2.硬件环境 处理器&#xff1a;Intel(R) Core(TM) i7-9750 GPU 3.00GHz 内存&#xff1a;8GB 3.软件环境 操作系统&…

【无标题】实时系统Preempt RT与Xenomai之争!谁更主流,谁更实时?

选择争论一直存在 大家知道EtherCAT是实时现场总线技术&#xff0c;当我们开发一款支持EtherCAT总线的控制器时&#xff0c;实时操作系统的选择不仅对于产品本身是最重要的一部分&#xff0c;而且对产品研发的整个过程也影响深远。 根据EtherCAT主站提供商Acontis公司对全球新客…

【ssh】pycharm链接远程服务器出现:Bad owner or permissions on C:\\Users\\用户名/.ssh/config

一直以来是用的pycharm&#xff0c;最近改用了vscode登录&#xff0c;并配置了config文件实现了vscode自动连接远程服务器&#xff0c;但是回到pycharm发现terminal端口不管用了&#xff0c;电脑上的powershell也是链接不上远程服务器并报错Bad owner or permissions。 【解决…