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

news2024/11/26 4:22:44

本篇是关于 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.coloradmin.cn/o/1671593.html

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

相关文章

jenkins+gitlab+sonar自由风格项目配置

新建项目&基本配置 gitlab侧配置 sonar.projectKeytest_sonar sonar.projectNametest_sonar sonar.projectVersion1.0 sonar.sources. sonar.exclusionssrc/layout/** sonar.sourceEncodingUTF-8 sonar.nodejs.executable/app/nodejs/node-v16.20.2-linux-x64/bin/node配置…

Git详解之六:Git工具

现在&#xff0c;你已经学习了管理或者维护 Git 仓库&#xff0c;实现代码控制所需的大多数日常命令和工作流程。你已经完成了跟踪和提交文件的基本任务&#xff0c;并且发挥了暂存区和轻量级的特性分支及合并的威力。 接下来你将领略到一些 Git 可以实现的非常强大的功能&…

Web APIs(获取元素+操作元素+节点操作)

目录 1.API 和 Web API 2.DOM导读 DOM树 3.获取元素 getElementById获取元素 getElementsByTagName获取元素 H5新增方法获取 获取特殊元素 4.事件基础 执行事件 操作元素 修改表单属性 修改样式属性 使用className修改样式属性 获取属性的值 设置属性的值 移除…

视频模糊变清晰,这13个工具总有一个能帮到你,收藏好

1、Topaz Video Enhance AI 这是一款非常专业的视频分辨率放大软件&#xff0c;使用来自多个帧的信息来实现视频升级、去噪、去隔行扫描和恢复的结果。 Topaz Video Enhance AI可以将视频放大和增强8K分辨率的镜头&#xff0c;并提供真实的细节和动作一致性。它采用AI技术实现…

数据库面试总结

数据库相关 mysql使用的函数 字符相关: concant() 连接字符 trim()去除字符的首尾空格 space(n) 返回n个空格 char_length() 返回字符的个数 ucase()/upper()将字符串 s 的所有字母变成大写字母 lcase()/lower() 将字符串 s 的所有字母变成小写字母 substr/substring/mid(s, …

prophet时间序列模型水质预测应用

前言 此前已经分析了&#xff0c;ARIMA 模型在水质预测中的应用&#xff0c;今天用 prophet 模型测试下在水质预测中的效果。 Prophet 简介 Prophet 是 Facebook 于2017年开源的一个时间序列预测框架&#xff0c;特别适合于处理具有明显趋势性和季节性的数据。该模型设计初衷…

AI算法-高数5.2-线性代数-向量间的线性相关、无关定义和结论

宋浩老师课程&#xff1a;3.2 向量间的线性关系&#xff08;二&#xff09;_哔哩哔哩_bilibili 线性相关、不相关结论&#xff1a; 判断线性有关\无关&#xff0c;转化成方程组&#xff1a; 判断条件> 向量线性相关、无关的本质是&#xff1a;除0外能不能找到非0的数据。

【吊打面试官系列】Java高并发篇 - 如何创建守护线程?

大家好&#xff0c;我是锋哥。今天分享关于 【如何创建守护线程&#xff1f;】面试题&#xff0c;希望对大家有帮助&#xff1b; 如何创建守护线程&#xff1f; 使用 Thread 类的 setDaemon(true)方法可以将线程设置为守护线程&#xff0c;需要注意的是&#xff0c;需要在调用 …

代码缺陷扫描神器——FindBugs

FindBugs目前&#xff0c;主要有三种形式使用&#xff0c;GUI形式、插件形式、Ant脚本形式&#xff0c;在这里只讲述FindBugs作为插件&#xff0c;在Android Studio中的应用。 目录 一、FindBugs基础知识 二、FindBugs使用进阶 网络安全学习路线 &#xff08;2024最新整理&am…

nginx目录枚举修复手册

nginx目录枚举修复手册 漏洞背景 修复方式: ssh zujian2 sudo vi /data/apps/nginx/conf/conf.d/default.conf server {

软件测试,功能测试转测开容易吗?

一、从这个问题&#xff0c;我能读出一些信息如下&#xff1a; 1、不知道您从事测试工作多久了&#xff0c;可以看出您特别羡慕测试开发工程师&#xff1b; 2、 您可能一直从事功能测试工作&#xff0c;工作模式或大环境下&#xff0c;被中了草&#xff0c;想学习测试开发相关…

动手学深度学习18 预测房价竞赛总结

动手学深度学习18 预测房价竞赛总结 李沐老师代码AutoGluonh2o集成学习automlQA 视频&#xff1a; https://www.bilibili.com/video/BV15Q4y1o7vc/?vd_sourceeb04c9a33e87ceba9c9a2e5f09752ef8 代码&#xff1a; https://www.bilibili.com/video/BV1rh411m7Hb/?vd_sourceeb04…

[C++核心编程-09]----C++类和对象之继承

&#x1f3a9; 欢迎来到技术探索的奇幻世界&#x1f468;‍&#x1f4bb; &#x1f4dc; 个人主页&#xff1a;一伦明悦-CSDN博客 ✍&#x1f3fb; 作者简介&#xff1a; C软件开发、Python机器学习爱好者 &#x1f5e3;️ 互动与支持&#xff1a;&#x1f4ac;评论 &…

python内置函数exec()和eval()区别

在Python中&#xff0c;eval() 和 exec() 都是内置函数&#xff0c;用于执行存储在字符串或对象中的Python代码&#xff0c;但它们之间也有一些区别。 eval() 语法&#xff1a;eval(expression, globalsNone, localsNone) expression&#xff1a;需要求值的字符串表达式。可…

【C++】 string类:应用与实践

&#x1f49e;&#x1f49e; 前言 hello hello~ &#xff0c;这里是大耳朵土土垚~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#x…

自定义类型——结构体、枚举和联合

自定义类型——结构体、枚举和联合 结构体结构体的声明匿名结构体结构体的自引用结构体的初始化结构体的内存对齐修改默认对齐数结构体传参 位段枚举联合 结构体 结构是一些值的集合&#xff0c;这些值被称为成员变量&#xff0c;结构的每个成员可以是不同类型的变量。 数组是…

Python爬虫入门:网络世界的宝藏猎人

今天阿佑将带你踏上Python的肩膀&#xff0c;成为一名网络世界的宝藏猎人&#xff01; 文章目录 1. 引言1.1 简述Python在爬虫领域的地位1.2 阐明学习网络基础对爬虫的重要性 2. 背景介绍2.1 Python语言的流行与适用场景2.2 网络通信基础概念及其在数据抓取中的角色 3. Python基…

线性表(2)

第二章、线性表&#xff08;linear list&#xff09; 线性表是第一个数据结构&#xff0c;再提一遍&#xff0c;学习一个具体的数据结构需要关注它的逻辑结构&#xff0c;物理结构和数据的运算&#xff0c;即三要素。 2.1、线性表的定义和基本操作 线性表的定义 需要注意的是…

如文所示:

影响 ConnectWise 的 ScreenConnect 远程桌面访问产品的严重漏洞已被广泛利用来传播勒索软件和其他类型的恶意软件。 ConnectWise 于 2 月 19 日通知客户&#xff0c;它已发布针对关键身份验证绕过缺陷和高严重性路径遍历问题的补丁。该安全漏洞当时没有 CVE 标识符。第二天&am…

Windows2016系统禁止关闭系统自动更新教程

目录 1.输入cmd--适合系统2016版本2.输入sconfig&#xff0c;然后按回车键3.输入5&#xff0c;然后按回车键4.示例需要设置为手动更新&#xff0c;即输入M&#xff0c;然后按回车键 1.输入cmd–适合系统2016版本 2.输入sconfig&#xff0c;然后按回车键 3.输入5&#xff0c;然后…