JUC第十三讲:JUC锁: ReentrantLock详解

news2025/1/12 20:40:15

JUC第十三讲:JUC锁: ReentrantLock详解

本文是JUC第十三讲,JUC锁:ReentrantLock详解。可重入锁 ReentrantLock 的底层是通过 AbstractQueuedSynchronizer 实现,所以先要学习上一章节 AbstractQueuedSynchronizer 详解。

文章目录

  • JUC第十三讲:JUC锁: ReentrantLock详解
    • 1、带着BAT大厂的面试问题去理解
    • 2、ReentrantLock源码分析
      • 2.1、类的继承关系
      • 2.2、类的内部类
      • 2.3、类的属性
      • 2.4、类的构造函数
      • 2.5、核心函数分析
    • 3、示例分析
      • 3.1、公平锁
    • 4、参考文章

1、带着BAT大厂的面试问题去理解

请带着这些问题继续后文,会很大程度上帮助你更好的理解相关知识点。

  • 什么是可重入,什么是可重入锁? 它用来解决什么问题? 一定程度避免死锁
  • ReentrantLock的核心是AQS,那么它怎么来实现的,继承吗? 说说其类内部结构关系。独占模式
  • ReentrantLock是如何实现公平锁的?
  • ReentrantLock是如何实现非公平锁的?
  • ReentrantLock默认实现的是公平还是非公平锁? 非公平
  • 使用ReentrantLock实现公平和非公平锁的示例?
  • ReentrantLock和Synchronized的对比?

2、ReentrantLock源码分析

2.1、类的继承关系

ReentrantLock实现了Lock接口,Lock接口中定义了lock与unlock相关操作,并且还存在newCondition方法,表示生成一个条件。

public class ReentrantLock implements Lock, java.io.Serializable

2.2、类的内部类

ReentrantLock总共有三个内部类,并且三个内部类是紧密相关的,下面先看三个类的关系。
image

说明:ReentrantLock类内部总共存在Sync、NonfairSync、FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自 AbstractQueuedSynchronizer 抽象类。下面逐个进行分析。

  • Sync类

Sync类的源码如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
    // 序列号
    private static final long serialVersionUID = -5179523762034025860L;
    
    // 获取锁
    abstract void lock();
    
    // 非公平方式获取
    final boolean nonfairTryAcquire(int acquires) {
        // 当前线程
        final Thread current = Thread.currentThread();
        // 获取状态
        int c = getState();
      	// 表示没有线程正在竞争该锁
        if (c == 0) {
          	// 比较并设置状态成功,状态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;
    }
    // 实现AQS提供的拓展点
    // 试图在共享模式下获取对象状态,此方法应该查询是否允许它在共享模式下获取对象状态,如果允许,则获取它
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
      	// 当前线程不为独占线程
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException(); // 抛出异常
        // 释放标识
        boolean free = false; 
        if (c == 0) {
            free = true;
            // 已经释放,清空独占
            setExclusiveOwnerThread(null); 
        }
        // 设置标识
        setState(c); 
        return free; 
    }
    
    // 判断资源是否被当前线程占有
    protected final boolean isHeldExclusively() {
        // While we must in general read state before owner,
        // we don't need to do so to check if current thread is owner
        return getExclusiveOwnerThread() == Thread.currentThread();
    }

    // 新生一个条件
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

    // Methods relayed from outer class
    // 返回资源的占用线程
    final Thread getOwner() {
        return getState() == 0 ? null : getExclusiveOwnerThread();
    }
    // 返回状态
    final int getHoldCount() {
        return isHeldExclusively() ? getState() : 0;
    }

    // 资源是否被占用
    final boolean isLocked() {
        return getState() != 0;
    }

    /**
     * Reconstitutes the instance from a stream (that is, deserializes it).
     */
    // 自定义反序列化逻辑
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        setState(0); // reset to unlocked state
    }
}  

Sync类存在如下方法和作用如下。

image

  • NonfairSync类

NonfairSync类继承了Sync类,表示采用非公平策略获取锁,其实现了Sync类中抽象的lock方法,源码如下:

// 非公平锁
static final class NonfairSync extends Sync {
    // 版本号
    private static final long serialVersionUID = 7316153563782823691L;

    // 获得锁
    final void lock() {
      	// 比较并设置状态成功,状态0表示锁没有被占用
        if (compareAndSetState(0, 1))
            // 把当前线程设置独占了锁
            setExclusiveOwnerThread(Thread.currentThread());
        else // 锁已经被占用,或者set失败
            // 以独占模式获取对象,忽略中断
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

说明:从lock方法的源码可知,每一次都尝试获取锁,而并不会按照公平等待的原则进行等待,让等待时间最久的线程获得锁。

  • FairSync类

FairSync类也继承了Sync类,表示采用公平策略获取锁,其实现了Sync类中的抽象lock方法,源码如下:

// 公平锁
static final class FairSync extends Sync {
    // 版本序列化
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        // 以独占模式获取对象,忽略中断
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    // 尝试公平获取锁  AQS抽象类提供的拓展点
    protected final boolean tryAcquire(int acquires) {
        // 获取当前线程
        final Thread current = Thread.currentThread();
        // 获取状态
        int c = getState();
        if (c == 0) { // 状态为0
          	// 不存在已经等待更久的线程 并且比较并且设置状态成功
            if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {
                // 设置当前线程独占
                setExclusiveOwnerThread(current);
                return true;
            }
        }
      	// 状态不为0,即资源已经被线程占据
        else if (current == getExclusiveOwnerThread()) {
            // 下一个状态
            int nextc = c + acquires;
            if (nextc < 0) // 超过了int的表示范围
                throw new Error("Maximum lock count exceeded");
            // 设置状态
            setState(nextc);
            return true;
        }
        return false;
    }
}

说明:跟踪lock方法的源码可知,当资源空闲时,它总是会先判断sync队列(AbstractQueuedSynchronizer中的数据结构)是否有等待时间更长的线程,如果存在,则将该线程加入到等待队列的尾部,实现了公平获取原则。其中,FairSync类的lock的方法调用如下,只给出了主要的方法。
image

说明:可以看出只要资源被其他线程占用,该线程就会添加到sync queue中的尾部,而不会先尝试获取资源。这也是和Nonfair最大的区别,Nonfair每一次都会尝试去获取资源,如果此时该资源恰好被释放,则会被当前线程获取,这就造成了不公平的现象,当获取不成功,再加入队列尾部。

2.3、类的属性

ReentrantLock类的sync非常重要,对 ReentrantLock 类的操作大部分都直接转化为对Sync和 AbstractQueuedSynchronizer 类的操作。

public class ReentrantLock implements Lock, java.io.Serializable {
    // 序列号
    private static final long serialVersionUID = 7373984872572414699L;    
    // 同步队列
    private final Sync sync;
}

2.4、类的构造函数

  • ReentrantLock() 型构造函数

默认是采用的非公平策略获取锁

public ReentrantLock() {
    // 默认非公平策略
    sync = new NonfairSync();
}
  • ReentrantLock(boolean) 型构造函数

可以传递参数确定采用公平策略或者是非公平策略,参数为true表示公平策略,否则,采用非公平策略:

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2.5、核心函数分析

通过分析ReentrantLock的源码,可知对其操作都转化为对Sync对象的操作,由于Sync继承了AQS,所以基本上都可以转化为对AQS的操作。如将ReentrantLock的lock函数转化为对Sync的lock函数的调用,而具体会根据采用的策略(如公平策略或者非公平策略)的不同而调用到Sync的不同子类。

所以可知,在ReentrantLock的背后,是AQS对其服务提供了支持,由于之前我们分析AQS的核心源码,遂不再累赘。下面还是通过例子来更进一步分析源码。

3、示例分析

3.1、公平锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class MyThread extends Thread {
    private Lock lock;
    public MyThread(String name, Lock lock) {
        super(name);
        this.lock = lock;
    }
    
    public void run () {
        lock.lock();
        try {
            System.out.println(Thread.currentThread() + " running");
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } finally {
            lock.unlock();
        }
    }
}

public class AbstractQueuedSynchronizerDemo {
    public static void main(String[] args) throws InterruptedException {
        Lock lock = new ReentrantLock(true);
        
        MyThread t1 = new MyThread("t1", lock);        
        MyThread t2 = new MyThread("t2", lock);
        MyThread t3 = new MyThread("t3", lock);
        t1.start();
        t2.start();    
        t3.start();
    }
}

运行结果(某一次):

Thread[t1,5,main] running
Thread[t2,5,main] running
Thread[t3,5,main] running

说明: 该示例使用的是公平策略,由结果可知,可能会存在如下一种时序。

image

说明: 首先,t1线程的lock操作 -> t2线程的lock操作 -> t3线程的lock操作 -> t1线程的unlock操作 -> t2线程的unlock操作 -> t3线程的unlock操作。根据这个时序图来进一步分析源码的工作流程。

  • t1线程执行lock.lock,下图给出了方法调用中的主要方法。
    image

说明: 由调用流程可知,t1线程成功获取了资源,可以继续执行。

  • t2线程执行 lock.lock,下图给出了方法调用中的主要方法。

image

说明: 由上图可知,最后的结果是t2线程会被禁止,因为调用了LockSupport.park。

  • t3线程执行lock.lock,下图给出了方法调用中的主要方法。

image
说明:由上图可知,最后的结果是t3线程会被禁止,因为调用了LockSupport.park。

  • t1线程调用了lock.unlock,下图给出了方法调用中的主要方法。
    image

说明:如上图所示,最后,head的状态会变为0,t2线程会被unpark,即t2线程可以继续运行。此时t3线程还是被禁止。

  • t2获得cpu资源,继续运行,由于t2之前被park了,现在需要恢复之前的状态,下图给出了方法调用中的主要方法。

image

说明:在setHead函数中会将head设置为之前head的下一个结点,并且将pre域与thread域都设置为null,在acquireQueued返回之前,sync queue就只有两个结点了。

  • t2执行lock.unlock,下图给出了方法调用中的主要方法。
    image

说明: 由上图可知,最终unpark t3线程,让t3线程可以继续运行。

  • t3线程获取cpu资源,恢复之前的状态,继续运行。

image

说明: 最终达到的状态是sync queue中只剩下了一个结点,并且该节点除了状态为0外,其余均为null。

  • t3执行lock.unlock,下图给出了方法调用中的主要方法。

image

说明: 最后的状态和之前的状态是一样的,队列中有一个空节点,头节点为尾节点均指向它。

使用公平策略和Condition的情况可以参考上一篇关于AQS的源码示例分析部分,不再累赘。

4、参考文章

  • 【JUC】JDK1.8源码分析之ReentrantLock(三)

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

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

相关文章

数据结构与算法基础(青岛大学-王卓)(8)

哎呀呀&#xff0c;sorry艾瑞波地&#xff0c;这次真的断更一个月了&#xff0c;又发生了很多很多事情&#xff0c;秋风开始瑟瑟了&#xff0c;老父亲身体查出肿瘤了&#xff0c;有病请及时就医&#xff0c;愿每一个人都有一个健康的身体&#xff0c;God bless U and FAMILY. 直…

实现简单BS架构案例

BS架构简单通俗理解 就是 浏览器–服务器模式&#xff0c;浏览器 充当 我们的客户端。 目录 简单BS架构实现案例基本原理视图访问规则案例要求改造前服务端线程模版类 改造后(优化)优化策略服务端线程模版类 参考视频 简单BS架构实现案例 基本原理视图 注&#xff1a;服务器必…

【VsCode】SSH远程连接Linux服务器开发,搭配cpolar内网穿透实现公网访问

文章目录 前言1、安装OpenSSH2、vscode配置ssh3. 局域网测试连接远程服务器4. 公网远程连接4.1 ubuntu安装cpolar内网穿透4.2 创建隧道映射4.3 测试公网远程连接 5. 配置固定TCP端口地址5.1 保留一个固定TCP端口地址5.2 配置固定TCP端口地址5.3 测试固定公网地址远程 前言 远程…

奥斯卡·王尔德

奥斯卡王尔德 奥斯卡王尔德&#xff08;Oscar Wilde&#xff0c;1854年10月16日—1900年11月30日&#xff09;&#xff0c;出生于爱尔兰都柏林&#xff0c;19世纪英国&#xff08;准确来讲是爱尔兰&#xff0c;但是当时由英国统治&#xff09;最伟大的作家与艺术家之一&#xf…

【Java 进阶篇】JDBC ResultSet 遍历结果集详解

在Java数据库编程中&#xff0c;经常需要执行SQL查询并处理查询结果。ResultSet&#xff08;结果集&#xff09;是Java JDBC中用于表示查询结果的关键类之一。通过遍历ResultSet&#xff0c;我们可以访问和操作从数据库中检索的数据。本文将详细介绍如何使用JDBC来遍历ResultSe…

手把手教你做个智能加湿器(一)

一、前言 目前常见的加湿器类电子产品一般是由PCBA和外壳组成&#xff0c;我们将从PCB设计&#xff0c;然后编写软件&#xff0c;接着设计外壳&#xff0c;设计出一个完整的产品出来。 需要使用到软件&#xff1a; Altium Designer 17 SolidWorks 2019 Keil 4 二…

C++--位图和布隆过滤器

1.什么是位图 所谓位图&#xff0c;就是用每一位来存放某种状态&#xff0c;适用于海量数据&#xff0c;数据无重复的场景。通常是用来判断某个数据存不存在的。比如int 有32位&#xff0c;就可以存放0到31这32个数字在不在某个文件中。当然&#xff0c;其他类型也可以。 2.位…

Python|OpenCV-如何给目标图像添加边框(7)

前言 本文是该专栏的第7篇,后面将持续分享OpenCV计算机视觉的干货知识,记得关注。 在使用opencv处理图像的时候,会不可避免的对图像的一些具体区域进行一些操作。比如说,想要给目标图像创建一个围绕图像的边框。简单的来说,就是在图片的周围再填充一个粗线框。具体效果,…

真红之刃攻略,真红之刃氪金攻略

真红之刃新手怎么玩&#xff1f;这款游戏有很多值得新手们了解的内容。下面我们来详细了解一下游戏的玩法。 关注【娱乐天梯】&#xff0c;获取真红之刃0.1折内部福利号 1、恶魔广场&#xff1a;这是奇迹中最受欢迎的玩法之一&#xff0c;也是每日必刷的副本之一。进入条件是1转…

(Note)机器学习面试题

机器学习 1.两位同事从上海出发前往深圳出差&#xff0c;他们在不同时间出发&#xff0c;搭乘的交通工具也不同&#xff0c;能准确描述两者“上海到深圳”距离差别的是&#xff1a; A.欧式距离 B.余弦距离 C.曼哈顿距离 D.切比雪夫距离 S:D 1. 欧几里得距离 计算公式&#x…

经典算法-----迷宫问题(栈的应用)

目录 前言 迷宫问题 算法思路 1.栈的使用方法 ​编辑2.方向的定义 代码实现 栈的cpp代码&#xff1a; 栈的头文件h代码: 走迷宫代码&#xff1a; 前言 今天学习一种算法问题&#xff0c;也就是我们常见的迷宫问题&#xff0c;本期我们通过前面学习过的数据结构---栈来…

一种基于体素的射线检测

效果 基于体素的射线检测 一个漏检的射线检测 从起点一直递增指定步长即可得到一个稀疏的检测 bool Raycast(Vector3 from, Vector3 forword, float maxDistance){int loop 6666;Vector3 pos from;Debug.DrawLine(from, from forword * maxDistance, Color.red);while (loo…

Vue3切换路由白屏刷新后才显示页面内容

问题所在&#xff1a; 1.首先检查页面路由以及页面路径配置是否配置错误。 2.如果页面刷新可以出来那证明不是配置的问题&#xff0c;其次检查是否在根组件标签最外层包含了个最大的div盒子包裹内容。&#xff08;一般vue3是可以不使用div盒子包裹的&#xff09; 3.最后如果…

快速开发微信小程序之一登录认证

一、背景 记得11、12年的时候大家一窝蜂的开始做客户端Android、IOS开发&#xff0c;我是14年才开始做Andoird开发&#xff0c;干了两年多&#xff0c;然后18年左右微信小程序火了&#xff0c;我也做了两个小程序&#xff0c;一个是将原有牛奶公众号的功能迁移到小程序&#x…

【iptables 实战】04 高级用法:iptables模块之state扩展

一、场景描述 假设我们内部的主机&#xff0c;想访问新闻网站&#xff0c;即访问其80端口。要想正确地获取到http报文内容。我们对这个网络报文应该这么设置 出去的报文&#xff0c;目标端口&#xff0c;80端口&#xff0c;放行进入的报文&#xff0c;源端口&#xff0c;80端…

vertx的学习总结2

一、什么是verticle verticle是vertx的基本单元&#xff0c;其作用就是封装用于处理事件的技术功能单元 &#xff08;如果不能理解&#xff0c;到后面的实战就可以理解了&#xff09; 二、写一个verticle 1. 引入依赖&#xff08;这里用的是gradle&#xff0c;不会吧&#…

几个推荐程序员养成的好习惯

本文框架 前言case1 不想当然case2 不为了解决问题而解决问题case3 不留问题死角case4 重视测试环节 前言 中秋国庆双节至&#xff0c;旅行or回乡探亲基本是大家的选择&#xff0c;看看风景或陪陪家人确实是个难得的机会。不过我的这次假期选择了闭关&#xff0c;不探亲&#…

手机号码(作为查询参数)格式校验:@PhoneQuery(自定义参数校验注解)

目标 自定义一个用于校验&#xff08;作为查询参数的&#xff09;手机号码格式的注解PhoneQuery&#xff0c;能够和现有的 Validation 兼容&#xff0c;使用方式和其他校验注解保持一致。 校验逻辑 可以为 null 或 空字符串&#xff1b;不能包含空格&#xff1b;必须为数字序…

开放式耳机哪个好、百元开放式耳机推荐品牌

开放式耳机以其独特的设计和出色的性能优势&#xff0c;成为许多音乐爱好者的首选。与传统的入耳式耳机相比&#xff0c;开放式耳机给用户带来更加舒适的佩戴体验。无需插入耳道&#xff0c;避免了长时间佩戴带来的不适感&#xff0c;让你可以尽情享受音乐而不受干扰。此外&…

Codeforces Round 901 (Div. 1) B. Jellyfish and Math(思维题/bfs)

题目 t(t<1e5)组样例&#xff0c;每次给出a,b,c,d,m(0<a,b,c,d,m<2的30次方) 初始时&#xff0c;(x,y)(a,b)&#xff0c;每次操作&#xff0c;你可以执行以下四种操作之一 ①xx&y&#xff0c;&为与 ②xx|y&#xff0c;|为或 ③yx^y&#xff0c;^为异或 …