JUC并发编程之读写锁原理

news2024/12/23 11:08:21

1.图解流程

读写锁用的是同一个 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) 让计数 -1,但由于计数还不为零

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

2. 源码分析(默认非公平锁)

写锁上锁流程

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; 
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }
		
    // 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; // writers can always barge
    }

写锁释放流程

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; 
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

	// 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 {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; 
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

	// 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)) {
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter 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);
    }
	
	// 非公平锁 readerShouldBlock 看 AQS 队列中第一个节点是否是写锁
	// true 该阻塞    false 则不阻塞
    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }

	
	// 从Sync 继承的方法
	// 与 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 (firstReader == current) {
                   
                } else {
                    if (rh == null) {
                        rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current)) {
                            rh = readHolds.get();
                            if (rh.count == 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)) {
                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; 
                }
                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; 
        // 设置自己为 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 {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; 
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

	// 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) {
        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))
                // 读锁的计数不会影响其它获取读锁线程, 但会影响其它获取写锁线程
                // 计数为 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,用来解决传播性
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                
            }
            if (h == head)                   
                break;
        }
    }

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

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

相关文章

多线程并发编程-线程篇

线程基础 什么是线程&#xff1f; 系统中的一个程序就是一个进程&#xff0c;每个进程中的最基本的执行单位&#xff0c;执行路径就是线程&#xff0c;线程是轻量化的进程。 什么是纤程&#xff1f; 绿色线程&#xff0c;由用户自己进行管理的而不是系统进行管理的&#xf…

【教程类】IDEA 打包 jar 包

最近有点累&#xff0c;写点简单的图文教程的东西来缓解一下 一、你需要知道的基础概念 了解了基础概念之后&#xff0c;可以让我们学习的更快更好哦 ~~ 1. jar JAR&#xff08;Java Archive&#xff09;是Java中一种常用的归档文件格式&#xff0c;也可以被视为一种压缩文…

学生就业统计表案例

主要分为三块&#xff1a; 渲染业务新增业务删除业务 一、根据持久化数据渲染页面 核心步骤: 读取localstorage 本地数据 如果有数据则转换为对象放到变量里面一会使用它渲染页面如果没有则用默认空数组 []为了测试效果&#xff0c;咱们可以先把initData 存入本地存储看效果…

android存储1--device解锁前的流程

android版本&#xff1a;android-11.0.0_r21http://aospxref.com/android-11.0.0_r21/ 一、主用户primary user的创建 开机后kernel启动第一个用户态进程init&#xff0c;init进程fork出zygote进程。zygote又fork出system server进程。http://aospxref.com/android-11.0.0_r2…

垃圾收集器面试总结(一)

垃圾收集器 Serial 收集器&#xff08;GC日志标识&#xff1a;DefNew&#xff09; Serial&#xff08;串行&#xff09;收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。 它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾…

[比赛简介]BirdCLEF-2023

比赛链接&#xff1a;BirdCLEF 2023 | Kaggle 比赛简介 鸟类是生物多样性变化的极好指标&#xff0c;因为它们具有高度流动性并且具有不同的栖息地要求。因此&#xff0c;物种组合和鸟类数量的变化可以表明恢复项目的成败。然而&#xff0c;经常在大面积地区进行传统的基于观…

你的车有通风座椅吗?新款奔驰S400升级原厂主副驾座椅通风

大家好&#xff0c;我是奔之升小志&#xff08;bzs878&#xff09;&#xff0c;专注名车原厂升级&#xff0c;欢迎戳戳右上角“”号关注一下&#xff0c;持续为您带来精彩改装案例。 座椅通风有什么用&#xff1f;能改善身体与座椅接触面空气流通&#xff0c;达到不出汗的效果…

Linux网络服务----SSH

文章目录 一 、SSH服务1.1 什么是SSH服务器&#xff1f;1.2 常用的SSH软件的介绍 二 、ssh的运用2.1 存放ssh服务端的配置文件2.2 ssh在Linux中的密码登录2.3 利用ssh协议传输文件和获取文件2.4 sftp远程访问操作 三 、 ssh密钥登录操作四 、TCP_wapper的原理和运用4.1 TCP_wap…

IP-GUARD能否实现打印指定文件时需经过管理员审批后才能打印?

支持。先设置禁止打印文档的策略,然后设置相关审批流程,再给到客户端相应的申请权限: 1、在控制台-高级-打印控制策略中,给需要进行打印管控的客户端设置以下策略: 动作:禁止 2、在控制台-申请管理-桌面申请管理-审批流程管理中,添加申请类型为打印的审批流程,指定审批人…

通过ADB实现移动端h5项目无线真机调试(超级简单!)

前言 做移动端h5项目的时候&#xff0c;电脑浏览器调试样式和效果&#xff0c;可能和真机展示出来的效果有差距&#xff0c;比如有的手机开启了home键&#xff0c;比如文字大小等样式有偏差。虽然可以通过手机扫描网页二维码在手机上看样式&#xff0c;但是和真机还是有区别。…

每天一道大厂SQL题【Day23】华泰证券真题实战(五)

每天一道大厂SQL题【Day23】华泰证券真题实战(五) 大家好&#xff0c;我是Maynor。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深大数据选手&#xff0c;深知SQL重要性&#xff0c;接下来我准备用100天时间&#xff0c;基于大数据岗面试中的经典SQL题&…

Veritas 与星辰天合的官方一体化方案来了

11&#xff1e;2&#xff0c;XSKY星辰天合联手 Veritas 贡献企业数据管理最佳实践。 近日&#xff0c;XSKY星辰天合以“科技联盟伙伴”身份亮相 2023 Veritas Solution Day&#xff0c;并宣布与 Veritas 推出联合解决方案。双方将携手为大型企业客户带来业界领先的数据存储与保…

CDH中的MySQL升级(RPM包方式)

CDH中的MySQL升级&#xff08;RPM包方式&#xff09; 1.下载官网的5.7中最新的版本&#xff0c;地址&#xff1a;MySQL 5.7.41 rpm下载地址 2.解压下载的tar包&#xff1a;tar -xvf mysql-5.7.41-1.el7.x86_64.rpm-bundle.tar 3.备份数据库 3.1 先停止MySQL服务&#xff1a;sy…

【测试开发】第一节.测开入门(附常考面试题)

文章目录 前言 一、什么是测试开发 1.1 常考面试题 二、软件测试的基础概念 2.1 需求 2.2 测试用例 3、BUG 三、生命周期 3.1 软件的生命周期 3.2 软件测试的生命周期 四、软件工程中的几种常见的开发模型 4.1 瀑布模型 4.2 螺旋模型 4.3 增量模型和迭代模型 4.4 敏捷…

【Windows10】〖问题〗Win10默认应用Web浏览器设置里出现两个Microsoft Edge图标,如何删掉空白图标?

〖问题〗Win10默认应用Web浏览器设置里出现两个Microsoft Edge图标&#xff0c;如何删掉空白图标&#xff1f; 问题 出现原因&#xff1a; 空白那个应该是旧版edge&#xff0c;可能是因为你曾经升级最新版Chromium的edge时&#xff0c;旧版本的edge并没有被系统清除干净所…

spring security (史上最全)

认证与授权&#xff08;Authentication and Authorization&#xff09; 一般意义来说的应用访问安全性&#xff0c;都是围绕认证&#xff08;Authentication&#xff09;和授权&#xff08;Authorization&#xff09;这两个核心概念来展开的。 即&#xff1a; 首先需要确定用…

计算机组成原理——第七章输入输出系统(下)

还君明珠双泪目&#xff0c;恨不相逢未嫁时 文章目录 前言7.3.2 中断的作用和原理7.3.3 多重中断7.3.4 程序中断方式7.3.5 DMA 方式 前言 本节除了对时间的计算考察比较多之外&#xff0c;其他的方面也有考察&#xff0c;同时中断的考点在操作系统中也有考察&#xff0c;机组里…

〖Python网络爬虫实战⑯〗- 网页解析利器parsel

订阅&#xff1a;新手可以订阅我的其他专栏。免费阶段订阅量1000 python项目实战 Python编程基础教程系列&#xff08;零基础小白搬砖逆袭) 说明&#xff1a;本专栏持续更新中&#xff0c;目前专栏免费订阅&#xff0c;在转为付费专栏前订阅本专栏的&#xff0c;可以免费订阅付…

【hello Linux】进程控制

目录 1. 进程创建 2. 进程终止 3. 进程常见的退出方法 4. 进程等待 5. 进程等待的方法 6. 获取子进程status Linux&#x1f337; 1. 进程创建 fork 函数初识 在 linux 中 fork 函数是非常重要的函数&#xff0c;它可以从已存在进程中创建一个新进程。 新进程便是我们所说的子进…

从0到1搭建react 工程化前端项目

一、npm init 初始化包管理 1.在使用该命令之前&#xff0c;创建一个文件夹&#xff0c;例如&#xff1a;reactDemo2.使用在电脑终端命令行工具中&#xff0c;找到1创建的文件夹&#xff0c;并转到改文件夹指定目录&#xff1b;3.执行 npm init4.如图所示&#xff1a; 5.执行命…