初探 JUC 并发编程:独占锁 ReentrantLock 底层源码解析

本篇是关于 JUC 并发包中独占锁 ReentrantLock 底层源码的解析,在阅读之前需要对 AQS 抽象队列有基本的了解。

文章目录

    • 1.1 类图结构
    • 1.2 获取锁
      • 1)void lock() 方法
      • 2)void lockInterruptibly() 方法
      • 3)boolean tryLock() 方法
      • 4)boolean tryLock(long timeout, TimeUnit unit)
    • 1.3 释放锁
      • 1.4 使用 ReentrantLock 的好处

ReentrantLock 是一个可重入的独占锁,同时只要一个线程获取到锁,其他线程获取锁的时候,会被阻塞并且放入锁的 AQS 阻塞队列;ReentrantLock 中实现了公平锁和非公平锁,使用默认构造方法得到的是非公平锁,类中提供了一个传入布尔值的构造方法, ReentrantLock(boolean fair) 使用该构造方法传入 true 得到的就是公平锁。

1.1 类图结构

先来看一下 ReentrantLock 的类图结构

在这里插入图片描述

ReentrantLock 最终还是通过 AQS 队列来实现的:

    /**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLock 中提供了以上的两种构造方法,其作用和引言中说的相同,即构造公平锁或者非公平锁,ReentrantLock 中的内部类 Sync 这个类直接继承了 AQS ,它的两个子类 FairSyncNonfairLock分别实现了公平锁和非公平锁, ReentrantLock 类中的核心方法都是调用这两个子中类的方法来实现的。

在这里 AQS 队列中的 state 代表锁的可重入次数,默认的情况下,state 的值为 0,表示锁没有被任何线程持有,当第一个线程获取锁的时候会尝试使用 CAS 操作将 state 的值设置为 1,如果这个操作成功了就将锁的持有者设置为这个线程。在第二次获取锁的时候这个值会被设置为 1,当线程释放锁的时候会使 state 减一,直到减为 0 的时候,线程将锁释放。

虽然是可重入锁,但是重入的次数并不是无限次,

    /**
     * The synchronization state.
     */
    private volatile int state;

AQS 中的 state 是用 int 声明的,重入的最大次数就是 int 的最大值,也就是
在这里插入图片描述

如果超过这个数字,就会因为越界使其变为负数,此时会抛出异常:

throw new Error("Maximum lock count exceeded");

比如我们写这样一段代码:

public class ReentrantLockSourceCode {
    static ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
        // 不断获取锁
            while (true) lock.lock();
        });
        thread.start();
    }
}

构造一个线程,在循环中不断的去请求锁,最终我们就会喜提这个异常:

在这里插入图片描述

1.2 获取锁

1)void lock() 方法

在上面的案例中我们已经使用过了这个方法,当线程希望获取锁的时候就去调用这个方法:

    public void lock() {
        sync.lock();
    }

这个方法实际上是委托给了 sync 执行的,sync 会在初始化的时候根据传入的值设置为公平锁或非公平锁,这里先来看使用的最多的非公平锁中的实现:


        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

先去使用 CAS 操作将值设置为 1,如果成功了就将当前线程设置为锁的持有者;反之则会调用 AQS 抽象类中的 acquire(1); 方法。

这里调用了 AQS 类中的 acquire 方法:

      public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
	    }

这个方法中首先调用了 tryAcquire(int acquires) 方法去尝试获取锁,如果获取失败了,就将线程放到阻塞队列中并挂起,AQS 作为抽象类,其中没有提供 tryAcquire 的具体实现,根据不同的需求在子类中实现相应的逻辑:

        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 锁没有被占有
            if (c == 0) {
            // 采用 CAS 操作修改 state
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 锁被当前线程持有
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires; // 增加 state
                if (nextc < 0) // 越界的情况
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            // 以上两种情况都不符合,返回 false
            return false;
        }

这样就完成了上锁的操作,来梳理一下这个方法的流程

线程尝试去获取锁,使用 CAS 操作尝试将 state 值从 0 修改为 1

  • 如果修改成功将锁的持有者设置为该线程
  • 如果失败了就有两种可能:当前线程持有锁和其他线程持有锁,调用 AQS 中的 acquire 方法
    1. 调用子类中实现的 tryAcquire 方法
      • 尝试再次使用 CAS 操作,如果修改失败了则判断当前锁是否被当前线程持有
        • 持有则递增 state
        • 反之直接返回 false
    2. tryAcquire 成功则直接返回,反之则将线程加入队列中并且阻塞挂起。

再来补充一下公平锁的 tryAcquire() 方法的实现:

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            // (1)
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

不同之处在于在上面的(1)位置加上了 hasQueuedPredecessors() 方法去判断队列中是否有前置的线程,如果有则不会去尝试获取锁,这样就保证了公平性。

2)void lockInterruptibly() 方法

这个方法和 lock 方法类似,它的不同之处在于,使用这个方法获取锁的线程对于 interrupt() 方法会做出立即反应,即抛出 InterruptedException 异常然后返回,而 lock 方法则是在阻塞结束后再去判断线程在挂起的过程中是否出现阻塞。

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

实际调用了抽象类中的 acquireInterruptibly 方法:

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        // 如果线程被中断,直接抛出异常
        if (Thread.interrupted()) 
            throw new InterruptedException();
        if (!tryAcquire(arg))
        // 调用 AQS 的可终端方法
            doAcquireInterruptibly(arg);
    }

doAcquireInterruptibly(arg); 方法当线程返回并发现阻塞的时候,会直接抛出异常,其他方法中则会返回一个布尔值来表示线程在阻塞过程中是否被中断,让开发者自己去设置处理的方式。

3)boolean tryLock() 方法

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }
    
        /**
         * Performs non-fair tryLock.  tryAcquire is implemented in
         * subclasses, but both need nonfair try for trylock method.
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

尝试获取锁,如果锁没有被其他线程持有,当前线程获取到锁并且返回 true,反之则返回 false;当获取失败了时候,这个方法并不会将线程放到阻塞队列。

4)boolean tryLock(long timeout, TimeUnit unit)

尝试获取锁,与上面方法不同的是,方法设置了超时时间,如果这个时间内还没有获取到锁,则会直接返回 false。

lock.tryLock(1000, TimeUnit.SECONDS);
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
            // 调用 AQS 中的 tryAcquireNanos 方法
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

1.3 释放锁

    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;
    }

调用了 AQS 队列中 release 方法,方法中调用了 tryRelease 方法来尝试释放锁,同样,这个方法没有在抽象类中实现,而是在子类中去实现,这个方法最终会返回锁是否空闲,如果空闲的话执行 unparkSuccessor 方法去解除下一个可被唤醒线程的挂起状态,让它们去争夺锁。

        protected final boolean tryRelease(int releases) {
            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; // 锁是否被线程持有
        }
    /**
     * 如果存在的话,唤醒 Successer 线程
     */
    private void unparkSuccessor(Node node) {
        /*
         * 在进行唤醒操作之前,尝试清零等待状态以便为唤醒操作做准备,
         * 即使这种操作可能失败或者状态在操作过程中被修改,也是可以接受的。
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * 要接触挂起状态的线程一般是下一个线程,但如果下一个线程被取消
				 * 或者为空,则从尾部开始遍历查找可以解除的线程
         */
        Node s = node.next;
        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);
    }

1.4 使用 ReentrantLock 的好处

  1. 可定时的锁等待ReentrantLock 提供了 tryLock(long timeout, TimeUnit unit) 方法,可以在指定的时间内等待获取锁。这在需要避免线程长时间等待的情况下很有用。
  2. 可中断的锁等待ReentrantLock 提供了 lockInterruptibly() 方法,允许在等待锁的过程中响应中断,这样可以避免线程长时间阻塞。
  3. 公平锁和非公平锁ReentrantLock 提供了公平锁和非公平锁两种模式,可以通过构造函数来选择。公平锁会按照线程请求锁的顺序来获取锁,而非公平锁则不保证顺序。而 synchronized 关键字锁默认是非公平的。
  4. 可重入性ReentrantLock 是可重入锁,同一个线程可以多次获取同一个锁而不会死锁。而 synchronized 关键字锁也是可重入的,但是必须在同一个方法或者代码块中。
  5. 条件变量ReentrantLock 提供了 Condition 接口(AQS 提供的),可以设置多个 Condition 对象来更好的控制线程的状态。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(✌)粤嵌—2024/5/10—删除链表的倒数第 N 个结点

代码实现&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* removeNthFromEnd(struct ListNode *head, int n) {if (head NULL || n 0) {return head;}int i n;struct ListNode …

MySQL·复合查询

目录 基本查询回顾 案例1&#xff1a;查询工资高于500或岗位为MANAGER的雇员&#xff0c;同时还要满足他们的姓名首字母为大写的J 案例2&#xff1a;按照部门号升序而雇员的工资降序排序 案例3&#xff1a;使用年薪进行降序排序 案例4&#xff1a;显示工资最高的员工的名字…

TPI 系列——1W,3KVDC隔离 定电压输入,稳压双路输出DC-DC模块电源

TPI系列产品是专门针对PCB上需要与输入电源隔离的电源应用场合而设计的。该产品适用于&#xff1a;1&#xff09;输入电源的电压变化≤5%&#xff1b;2&#xff09;输入输出之间要求隔离电压≥3000VDC&#xff1b;3&#xff09;对输出电压稳定和输出纹波噪声要求高.

多商户Docker Supervisor进程管理器部署

Dockerfile 根目录下没有Dockerfile的可以复制下面的命令 # 使用基础镜像 FROM leekay0218/crmeb-mer## 复制代码 ## 在本地调试注释掉&#xff0c;使用映射把文件映射进去 #ADD ./ /var/www# 设置工作目录 WORKDIR /var/www# 设置时区为上海 ENV TZAsia/Shanghai RUN ln -sn…

Porto主题下载: 打造您网站的独特魅力

在数字时代&#xff0c;一个吸引人的网站是您品牌故事的开端。Porto&#xff0c;一款专为追求卓越设计和功能性的WordPress主题&#xff0c;让您的网站从众多竞争者中脱颖而出。 响应式设计 Porto主题采用最先进的响应式设计技术&#xff0c;确保您的网站在任何设备上都能提供…

Hive两代命令行客户端(Hive、Beeline)

Hive命令行客户端 Hive有两个主要的客户端工具&#xff0c;分别是旧版的Hive CLI&#xff08;Command Line Interface&#xff09;和新版的Beeline。 1. Hive CLI&#xff1a; Hive CLI 是 Hive 最早期的命令行客户端工具&#xff0c;它使用 JDBC 连接到 Hive 服务器&#xff…

[嵌入式系统-72]:ARM芯片选型基础

目录 一、ARM概述 1.1 ARM介绍 1.2 ARM芯片的特点 1.3 ARM芯片的商业模式 1.4 ARM的发展历史 二、ARM架构 2.1 ARM SOC芯片的架构 2.2 ARM核的架构 三、ARM核的架构演进 3.1 经典ARM处理器ARMx 3.2 嵌入式ARM处理器Cortex-Mx系列&#xff1a;微控制器 3.3 嵌入式AR…

AI图书推荐:重塑—利用生成式AI构建产品

你知道吗&#xff0c;将人工智能融入产品已经成为全球企业的关键战略&#xff1f;埃森哲 (Accenture) 2023 年的一项研究显示&#xff0c;高达 75%的高管认为&#xff0c;如果在未来五年内未能有效整合人工智能&#xff0c;企业可能会被淘汰。 《重塑&#xff1a;利用生成式人工…

魔法程序员的奥妙指南:Java基本语法

作为一名魔法程序员&#xff0c;精通Java语言是至关重要的。Java作为一种强大的编程语言&#xff0c;在编写优质代码和开发强大应用程序时发挥着重要作用。让我们深入探讨Java基本语法的关键要点&#xff0c;从注释到变量&#xff0c;无所不包&#xff01; Java基本语法的神秘魔…

可重入分布式锁有哪些应用场景

原文连接&#xff1a;可重入分布式锁有哪些应用场景 https://mp.weixin.qq.com/s/MTPS9V8jn5J91wr-UD4DyA 之前发过的一篇实现Redis分布式锁的8大坑中&#xff0c;有粉丝留言说&#xff0c;分布式锁的可重入特性在工作中有哪些应用场景&#xff0c;那么我们这篇文章就来看一下…

IP定位技术在打击网络犯罪中的作用

随着互联网的普及和信息技术的发展&#xff0c;网络犯罪日益猖獗&#xff0c;给社会治安和个人财产安全带来了严重威胁。而IP定位技术的应用为打击网络犯罪提供了一种有效手段。IP数据云将探讨IP定位技术在打击网络犯罪中的作用及其意义。 1. IP定位技术的原理 IP&#xff08…

Windows下安装httpd

一、下载http安装包 1、下载地址 Welcome! - The Apache HTTP Server Project 2、点击“Download” 3、选择对应httpd服务&#xff0c;点击“Files for Microsoft Windows” 4、选择“Apache Lounge”&#xff0c;进入下载页面 5、点击“httpd-2.4.59-240404-win64-VS17.zip …

日本站群服务器提升网站用户体验的选择

日本站群服务器提升网站用户体验的选择 在当今数字化时代&#xff0c;网站的性能和用户体验对于在线业务的成功至关重要。为了确保网站能够提供快速、可靠和高效的访问体验&#xff0c;越来越多的网站管理员和企业选择了使用站群服务器。本文将深入探讨日本站群服务器的独特优…

网络安全之OSI七层模型详解

OSI七层模型分为控制层&#xff08;前三层&#xff09;和数据层&#xff08;后四层&#xff09;。从第七层到一层为&#xff1b; 应用层&#xff08;7&#xff09;接收数据 表示层&#xff08;6&#xff09;将数据翻译为机器语言 会话层&#xff08;5&#xff09;建立虚连接…

如何编辑百度百科并提供参考资料

大家都知道参考资料是创建百度百科中最重要的一步&#xff0c;百度百科只收录可以找到资料来源的事实&#xff0c;参考资料的意义在于&#xff0c;指出该部分内容的来源/出处&#xff0c;从而保障这段内容是客观真实的。 注册和登录百度账号 首先&#xff0c;你需要在百度百科…

RuoYi-Vue-Plus (Echarts 图表)

一、echarts 图表介绍和使用 官网地址:目前echarts以及贡献给Apache Apache EChartshttps://echarts.apache.org/zh/index.htmlecharts配置项手册 Documentation - Apache EChartshttps://echarts.apache.org/z

台球桌上的答案 如何优化图形化编程对复杂程序的展现

在公司的休息区&#xff0c;卧龙和凤雏正站在台球桌旁&#xff0c;一场激战即将打响。 “来吧&#xff0c;凤雏&#xff0c;让我们一决高下&#xff01;”卧龙手持台球杆&#xff0c;面带自信的微笑&#xff0c;向凤雏发起挑战。 凤雏点了点头&#xff0c;拿起台球杆&#xff0…

泰迪科技2024中职大数据实训室方案解读

中职在大数据专业建设所遇到的困难 数据、信息安全、人工智能等新信息技术产业发展迅猛&#xff0c;人才极其匮乏&#xff0c;各个中职院校纷纷开设相应的专业方向。但是&#xff0c;绝大多数院校因为师资和积累问题&#xff0c;在专业建设规划、办学特色提炼、创新教学模…

Objective-C的对象复制与拷贝选项

对象复制与拷贝 文章目录 对象复制与拷贝copy与mutablecopycopy与mutablecopy的简介示例&#xff1a;不可变对象的复制可变对象的复制 NSCopying和NSMutableCopying协议深复刻和浅复刻浅拷贝&#xff08;Shallow Copy&#xff09;&#xff1a;深拷贝&#xff08;Deep Copy&…

AI智能分析高精度烟火算法EasyCVR视频方案助力打造森林防火建设

一、背景 随着夏季的来临&#xff0c;高温、干燥的天气条件使得火灾隐患显著增加&#xff0c;特别是对于广袤的森林地区来说&#xff0c;一旦发生火灾&#xff0c;后果将不堪设想。在这样的背景下&#xff0c;视频汇聚系统EasyCVR视频融合云平台AI智能分析在森林防火中发挥着至…
最新文章