非公平锁实现原理+源码解读

news2024/11/17 23:45:16

目录

非公平锁实现原理

加锁解锁流程

加锁源码 

解锁源码 


非公平锁实现原理

加锁解锁流程

先从构造器开始看,默认为非公平锁实现

public ReentrantLock() {
    sync = new NonfairSync();
}

NonfairSync 继承自 AQS

没有竞争时

 第一个竞争出现时

Thread-1 执行了

1. CAS 尝试将 state 由 0 改为 1,结果失败

2. 进入 tryAcquire 逻辑,这时 state 已经是1,结果仍然失败

3. 接下来进入 addWaiter 逻辑,构造 Node 队列

  • 图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态
  • Node 的创建是懒惰的
  • 其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程

当前线程进入 acquireQueued 逻辑

  • 1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
  • 2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  • 3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1,这次返回 false

 

  • 4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
  • 5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回 true 6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

 再次有多个线程经历上述过程竞争失败,变成这个样子

当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程 找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1

回到 Thread-1 的 acquireQueued 流程

如果加锁成功(没有竞争),会设置

  • exclusiveOwnerThread 为 Thread-1,state = 1
  • head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
  • 原本的 head 因为从链表断开,而可被垃圾回收

如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了

如果不巧又被 Thread-4 占了先

Thread-4 被设置为 exclusiveOwnerThread,state = 1

Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞

加锁源码 

// Sync 继承自 AQS

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        // 加锁实现
        final void lock() {
            // 首先用 cas 尝试(仅尝试一次)将 state 从 0 改为 1, 如果成功表示获得了独占锁

            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 如果尝试失败,进入 ㈠
                acquire(1);
        }
        // ㈠ AQS 继承过来的方法, 方便阅读, 放在此处
        public final void acquire(int arg) {
            // ㈡ tryAcquire
            if (!tryAcquire(arg) &&
                            // 当 tryAcquire 返回为 false 时, 先调用 addWaiter ㈣, 接着 acquireQueued ㈤
                            acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
            ) {
                selfInterrupt();
            }
        }

        // ㈡ 进入 ㈢
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

        // ㈢ Sync 继承过来的方法, 方便阅读, 放在此处
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 如果还没有获得锁

            if (c == 0) {
                // 尝试用 cas 获得, 这里体现了非公平性: 不去检查 AQS 队列

                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入

            else if (current == getExclusiveOwnerThread()) {
                // state++

                int nextc = c + acquires;
                if (nextc < 0) // overflow

                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 获取失败, 回到调用处

            return false;
        }

        // ㈣ AQS 继承过来的方法, 方便阅读, 放在此处

        private Node addWaiter(Node mode) {
// 将当前线程关联到一个 Node 对象上, 模式为独占模式

            Node node = new Node(Thread.currentThread(), mode);
            // 如果 tail 不为 null, cas 尝试将 Node 对象加入 AQS 队列尾部

            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    // 双向链表

                    pred.next = node;
                    return node;
                }
            }
            // 尝试将 Node 加入 AQS, 进入 ㈥

            enq(node);
            return node;
        }

        // ㈥ AQS 继承过来的方法, 方便阅读, 放在此处

        private Node enq(final Node node) {
            for (; ; ) {
                Node t = tail;
                if (t == null) {
                    // 还没有, 设置 head 为哨兵节点(不对应线程,状态为 0)

                    if (compareAndSetHead(new Node())) {
                        tail = head;
                    }
                } else {
                    // cas 尝试将 Node 对象加入 AQS 队列尾部

                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }

        // ㈤ AQS 继承过来的方法, 方便阅读, 放在此处

        final boolean acquireQueued(final Node node, int arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (; ; ) {
                    final Node p = node.predecessor();
                    // 上一个节点是 head, 表示轮到自己(当前线程对应的 node)了, 尝试获取

                    if (p == head && tryAcquire(arg)) {
                        // 获取成功, 设置自己(当前线程对应的 node)为 head

                        setHead(node);
                        // 上一个节点 help GC

                        p.next = null;
                        failed = false;
                        // 返回中断标记 false

                        return interrupted;
                    }
                    if (
                        // 判断是否应当 park, 进入 ㈦

                            shouldParkAfterFailedAcquire(p, node) &&

                                    // park 等待, 此时 Node 的状态被置为 Node.SIGNAL ㈧

                                    parkAndCheckInterrupt()
                    ) {
                        interrupted = true;
                    }
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

        // ㈦ AQS 继承过来的方法, 方便阅读, 放在此处
        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            // 获取上一个节点的状态
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL) {
                // 上一个节点都在阻塞, 那么自己也阻塞好了
                return true;
            }
            // > 0 表示取消状态
            if (ws > 0) {
                // 上一个节点取消, 那么重构删除前面所有取消的节点, 返回到外层循环重试
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                // 这次还没有阻塞
                // 但下次如果重试不成功, 则需要阻塞,这时需要设置上一个节点状态为 Node.SIGNAL
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }

        // ㈧ 阻塞当前线程
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }
    }

注意 是否需要 unpark 是由当前节点的前驱节点的 waitStatus == Node.SIGNAL 来决定,而不是本节点的 waitStatus 决定

解锁源码 

// Sync 继承自 AQS

    static final class NonfairSync extends Sync {
        // 解锁实现
        public void unlock() {
            sync.release(1);
        }

        // AQS 继承过来的方法, 方便阅读, 放在此处
        public final boolean release(int arg) {
            // 尝试释放锁, 进入 ㈠
            if (tryRelease(arg)) {
                // 队列头节点 unpark
                Node h = head;
                if (
                    // 队列不为 null
                        h != null &&
                                // waitStatus == Node.SIGNAL 才需要 unpark
                                h.waitStatus != 0
                ) {
                    // unpark AQS 中等待的线程, 进入 ㈡

                    unparkSuccessor(h);
                }
                return true;
            }
            return false;
        }

        // ㈠ Sync 继承过来的方法, 方便阅读, 放在此处

        protected final boolean tryRelease(int releases) {
            // state--
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 支持锁重入, 只有 state 减为 0, 才释放成功

            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

        // ㈡ AQS 继承过来的方法, 方便阅读, 放在此处

        private void unparkSuccessor(Node node) {
            // 如果状态为 Node.SIGNAL 尝试重置状态为 0
            // 不成功也可以
            int ws = node.waitStatus;
            if (ws < 0) {
                compareAndSetWaitStatus(node, ws, 0);
            }
            // 找到需要 unpark 的节点, 但本节点从 AQS 队列中脱离, 是由唤醒节点完成的
            Node s = node.next;
            // 不考虑已取消的节点, 从 AQS 队列从后至前找到队列最前面需要 unpark 的节点
            if (s == null || s.waitStatus > 0) {
                s = null;
                for (Node t = tail; t != null && t != node; t = t.prev)
                    if (t.waitStatus <= 0)
                        s = t;
            }
            if (s != null)
                LockSupport.unpark(s.thread);
        }
    }

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

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

相关文章

解决不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接的问题

问题概述&#xff1a; 用windows server 2012 r2 vl x64搭了个文件服务器&#xff0c;在使用时有个问题&#xff0c;老是用户登录有问题&#xff0c;提示“不允许一个用户使用一个以上用户名与一个服务器或共享资源的多重连接”。出现的原因不详&#xff0c;网上也没查到合理的…

操作系统3:CPU任务调度和进程调度

目录 1、处理机调度的层次 &#xff08;1&#xff09;高级调度(High Level Scheduling) &#xff08;2&#xff09;低级调度(Low Level Scheduling) &#xff08;3&#xff09;中级调度(Intermediate Scheduling) 2、处理机调度算法的目标 批处理系统的目标 3、作业与作…

MongoDB 基于角色的访问控制

一、概述 MongoDB采用基于角色的访问控制&#xff08;RBAC&#xff09;来管理对 MongoDB系统。向用户授予一个或多个角色那 确定用户对数据库资源和操作的访问权限。外面 在角色分配中&#xff0c;用户无权访问系统。 1、启用访问控制 MongoDB默认情况下不启用访问控制。您可…

Facebook Insights分析工具解读,掌握关键数据指标

什么是Facebook Insights&#xff1f; Facebook Insights是Facebook平台上的一项内置分析工具&#xff0c;旨在帮助企业和品牌了解其在Facebook上的表现和受众互动情况。该工具提供了丰富的数据和指标&#xff0c;可以帮助用户洞察粉丝群体、了解发布内容的表现&#xff0c;并…

闭门造轮(LVGL_2)

例程1_// 组件(widgets)&#xff1a; 标签(label)的用法 static void label_event_cb(lv_event_t * e) {lv_obj_t * obj lv_event_get_target(e); // 获取触发事件的部件(对象)lv_event_code_t code lv_event_get_code(e); // 获取当前部件(对象)触发的事件代码sw…

Golang每日一练(leetDay0105) 最小高度树、戳气球

目录 310. 最小高度树 Minimum Height Trees &#x1f31f;&#x1f31f; 312. 戳气球 Burst Balloons &#x1f31f;&#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一…

用自定义域名访问Tailscale节点

需求 tailscale 是好东西&#xff0c;在任何地方都可以和在局域网访问一样&#xff0c;但是也有着 IP 访问的不便&#xff0c;一方面 IP 是 tailscale 分配的&#xff08;非子网路由模式&#xff09;&#xff0c;另一方面还要记住各种端口 tailscale 也考虑到了这些问题&…

SpringBoot 使用 TestRestTemplate 进行 RESTful API 集成测试

SpringBoot 使用 TestRestTemplate 进行 RESTful API 集成测试 RESTful API 集成测试是测试应用程序与其外部依赖项之间的集成。SpringBoot提供了TestRestTemplate来测试RESTful API&#xff0c;本文将介绍如何使用TestRestTemplate进行RESTful API集成测试。 1. 什么是 TestR…

java File类 和 IO流

File类 文件和文件夹(文件路径)的抽象表示&#xff0c;是专门来出来磁盘上面的文件或文件夹的 构造方法 方法 返回boolean creatNewFile() 生成一个文件&#xff0c;当且仅当具有该名称的文件尚不存在时 public class Demo02 {public static void main(String[] args) th…

知了汇智网安项目实训助力高校实战型人才培养

当前&#xff0c;随着信息技术的高速发展和网络的普及应用&#xff0c;网络安全面临着日益复杂和严峻的挑战。恶意黑客、网络病毒、数据泄露等安全威胁不断涌现&#xff0c;给个人、企业和国家的信息资产和社会稳定带来了巨大风险。新形势之下&#xff0c;培养网络安全实战型人…

深眸科技受邀参加昆山元宇宙装备展与产业论坛,为工业视觉注智赋能

为落实《“十四五”数字经济发展规划》&#xff0c;进一步夯实元宇宙在终端装备领域的落地与拓展&#xff0c;江苏昆山将于6月27至29日&#xff0c;举办“2023元宇宙装备展”。本届展会由昆山市人民政府主办&#xff0c;昆山市工业和信息化局、赛迪工业和信息化研究院集团&…

力扣题库刷题笔记8--字符串转换正数(atoi)

1、题目如下&#xff1a; 2、个人Python代码实现如下&#xff1a; 这里可以看到&#xff0c;解答错误很多次&#xff0c;实际上就是对于题目的条件读的不够细&#xff0c;导致很多边界值的用例跑不过。而且个人习惯极其不好&#xff0c;没有输入各种可能的输入进行调试&#xf…

【计算机网络自顶向下】计算机网络期末自测题(一)答案

2019-2020 学年第 2 学期自测题答案及评分标准 (卷 1) 计算机网络 一、 填空题&#xff1a; 参考答案&#xff1a; 1 、 01000101 、11100111 3 、 100Mbps、双绞线、基带、全双工 [10Mbps 要求单位] 4 、 报文 5 、 ICMP 6 、 虚电路 7 、 距离矢量、链路状态 …

考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联合配置方法(matlab代码)

目录 1 主要内容 目标函数 电动汽车负荷建模 算例系统图 程序亮点 2 部分代码 3 程序结果 ​4 下载链接 1 主要内容 该程序复现博士文章《互动环境下分布式电源与电动汽车充电站的优化配置方法研究》第四章《考虑充电负荷空间可调度特性的分布式电源与电动汽车充电站联…

【抗扰PID控制】干扰抑制PID控制器研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Excel管理工具简陋但强大 vs 雷军为何说“没有设计是最好的设计

解密&#xff1a;雷军为何说“没有设计是最好的设计” 雷军洗白&#xff1a;没有刻意的设计才是最好的设计&#xff01; 有的人开发了10个到50个&#xff0c;甚至几百个Excel管理工具。每个工具里面有VBA代码几千行。 还有个高手&#xff0c;趁过年一个月左右时间完成一个公司用…

SAP ABAP DIALOG 表格控件的简单例子<转载>

本文是一个很见到的ALV表格控件例子&#xff0c;但是原文作者写的很详细&#xff0c;所以借过来一用&#xff0c;很适合新手练手用。在这里感谢原文作者大大&#xff01; 原文链接&#xff1a;https://mp.weixin.qq.com/s/bhP3w5DIADdf9P624C5kpw 表格控件是ABAP编程中最常用的…

0基础学习VR全景平台篇第48篇:高级功能-密码访问

功能位置示意 一、本功能将用在哪里&#xff1f; 密码访问功能&#xff0c;常用于暂未交付的项目&#xff0c;使用密码访问保护作品数据的私密性&#xff1b; 或为满足不同情境下的推广需求使用。 二、如何使用本功能&#xff1f; 1、选择-密码访问功能&#xff1b; 2、输入…

nvm使用大全nvm如何动态切换node版本

注意&#xff1a; nvm use node版本时&#xff0c;要使用管理员权限打开cmd输入命令&#xff0c;否则报错 常用命令 nvm ls &#xff1a;列出所有已安装的 node 版本 nvm list &#xff1a;列出所有已安装的 node 版本 nvm list available &#xff1a;显示所有可下载的版本 …

较旧系统的轻量级的LINUX发行版—FATDOG64

导读我们回顾FatDog64 Linux&#xff0c;这个轻量级的Linux发行版可能不是现代的&#xff0c;但它非常适合于老化的计算机&#xff0c;fatdog64 Linux是一个小而灵活的64位多用户Linux发行版。 有一个Linux发行版几乎每一个都需要&#xff0c;有Linux分布的黑客&#xff0c;天…