当前位置:

首页 > 编程开发 > java底层组合AQS实现类kReentrantLock源码分析

java底层组合AQS实现类kReentrantLock源码分析

1、类注释ReentrantLock中文我们习惯叫做可重入互斥锁,可重入的意思是同一个线程可以对同一个共享资源重复的加锁或释放锁,互斥就是AQS中的排它锁的意思,只允许一个线程获得锁。我们来一起来看下类注释上都有哪些重要信息:可重入互斥锁,和synchronized锁具有同样的功能语义,但更有扩展性;构造器接受fairness的参数,fairness是ture时,保证获得锁时的顺序

本文目录

    1、类注释

    ReentrantLock 中文我们习惯叫做可重入互斥锁,可重入的意思是同一个线程可以对同一个共享资源重复的加锁或释放锁,互斥就是 AQS 中的排它锁的意思,只允许一个线程获得锁。

    我们来一起来看下类注释上都有哪些重要信息:

    可重入互斥锁,和 synchronized 锁具有同样的功能语义,但更有扩展性;构造器接受 fairness 的参数,fairness 是 ture 时,保证获得锁时的顺序,false 不保证;公平锁的吞吐量较低,获得锁的公平性不能代表线程调度的公平性;tryLock() 无参方法没有遵循公平性,是非公平的(lock 和 unlock 都有公平和非公平,而 tryLock 只有公平锁,所以单独拿出来说一说)。

    我们补充一下第二点,ReentrantLock 的公平和非公平,是针对获得锁来说的,如果是公平的,可以保证同步队列中的线程从头到尾的顺序依次获得锁,非公平的就无法保证,在释放锁的过程中,我们是没有公平和非公平的说法的。

    2、类结构

    ReentrantLock 类本身是不继承 AQS 的,实现了 Lock 接口,如下:

    public class ReentrantLock implements Lock, java.io.Serializable {}

    Lock 接口定义了各种加锁,释放锁的方法,接口有如下几个:

    // 获得锁方法,获取不到锁的线程会到同步队列中阻塞排队void lock();// 获取可中断的锁void lockInterruptibly() throws InterruptedException;// 尝试获得锁,如果锁空闲,立马返回 true,否则返回 falseboolean tryLock();// 带有超时等待时间的锁,如果超时时间到了,仍然没有获得锁,返回 falseboolean tryLock(long time, TimeUnit unit) throws InterruptedException;// 释放锁void unlock();// 得到新的 ConditionCondition newCondition();

    ReentrantLock 就负责实现这些接口,我们使用时,直接面对的也是这些方法,这些方法的底层实现都是交给 Sync 内部类去实现的,Sync 类的定义如下:

    abstract static class Sync extends AbstractQueuedSynchronizer {}

    Sync 继承了 AbstractQueuedSynchronizer ,所以 Sync 就具有了锁的框架,根据 AQS 的框架,Sync 只需要实现 AQS 预留的几个方法即可,但 Sync 也只是实现了部分方法,还有一些交给子类 NonfairSync 和 FairSync 去实现了,NonfairSync 是非公平锁,FairSync 是公平锁,定义如下:

    // 同步器 Sync 的两个子类锁static final class FairSync extends Sync {}static final class NonfairSync extends Sync {}

    几个类整体的结构如下:

    java底层组合AQS实现类kReentrantLock源码分析

    图中 Sync、NonfairSync、FairSync 都是静态内部类的方式实现的,这个也符合 AQS 框架定义的实现标准。

    3、构造器 

    ReentrantLock 构造器有两种,代码如下:

    // 无参数构造器,相当于 ReentrantLock(false),默认是非公平的public ReentrantLock() {    sync = new NonfairSync();} public ReentrantLock(boolean fair) {    sync = fair ? new FairSync() : new NonfairSync();}

    无参构造器默认构造是非公平的锁,有参构造器可以选择。

    从构造器中可以看出,公平锁是依靠 FairSync 实现的,非公平锁是依靠 NonfairSync 实现的。

    4、Sync 同步器

    Sync 表示同步器,继承了 AQS,UML 图如下:

    java底层组合AQS实现类kReentrantLock源码分析

    从 UML 图中可以看出,lock 方法是个抽象方法,留给 FairSync 和 NonfairSync 两个子类去实现,我们一起来看下剩余重要的几个方法。

    4.1、nonfairTryAcquire 
    // 尝试获得非公平锁final boolean nonfairTryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    // 同步器的状态是 0,表示同步器的锁没有人持有    if (c == 0) {        // 当前线程持有锁        if (compareAndSetState(0, acquires)) {            // 标记当前持有锁的线程是谁            setExclusiveOwnerThread(current);            return true;        }    }    // 如果当前线程已经持有锁了,同一个线程可以对同一个资源重复加锁,代码实现的是可重入锁    else if (current == getExclusiveOwnerThread()) {        // 当前线程持有锁的数量 + acquires        int nextc = c + acquires;        // int 是有最大值的,<0 表示持有锁的数量超过了 int 的最大值        if (nextc < 0) // overflow            throw new Error("Maximum lock count exceeded");        setState(nextc);        return true;    }    //否则线程进入同步队列    return false;}

    以上代码有三点需要注意:

    通过判断 AQS 的 state 的状态来决定是否可以获得锁,0 表示锁是空闲的;else if 的代码体现了可重入加锁,同一个线程对共享资源重入加锁,底层实现就是把 state + 1,并且可重入的次数是有限制的,为 Integer 的最大值;这个方法是非公平的,所以只有非公平锁才会用到,公平锁是另外的实现。

    无参的 tryLock 方法调用的就是此方法,tryLock 的方法源码如下:

    public boolean tryLock() {    // 入参数是 1 表示尝试获得一次锁    return sync.nonfairTryAcquire(1);}

    4.1、tryRelease
    // 释放锁方法,非公平和公平锁都使用protected final boolean tryRelease(int releases) {    // 当前同步器的状态减去释放的个数,releases 一般为 1    int c = getState() - releases;    // 当前线程根本都不持有锁,报错    if (Thread.currentThread() != getExclusiveOwnerThread())        throw new IllegalMonitorStateException();    boolean free = false;    // 如果 c 为 0,表示当前线程持有的锁都释放了    if (c == 0) {        free = true;        setExclusiveOwnerThread(null);    }    // 如果 c 不为 0,那么就是可重入锁,并且锁没有释放完,用 state 减去 releases 即可,无需做其他操作    setState(c);    return free;}

    tryRelease 方法是公平锁和非公平锁都公用的,在锁释放的时候,是没有公平和非公平的说法的。

    从代码中可以看到,锁最终被释放的标椎是 state 的状态为 0,在重入加锁的情况下,需要重入解锁相应的次数后,才能最终把锁释放,比如线程 A 对共享资源 B 重入加锁 5 次,那么释放锁的话,也需要释放 5 次之后,才算真正的释放该共享资源了。

    5、FairSync 公平锁

    FairSync 公平锁只实现了 lock 和 tryAcquire 两个方法,lock 方法非常简单,如下:

    // acquire 是 AQS 的方法,表示先尝试获得锁,失败之后进入同步队列阻塞等待final void lock() {    acquire(1);}

    tryAcquire 方法是 AQS 在 acquire 方法中留给子类实现的抽象方法,FairSync 中实现的源码如下:

    protected final boolean tryAcquire(int acquires) {    final Thread current = Thread.currentThread();    int c = getState();    if (c == 0) {        // hasQueuedPredecessors 是实现公平的关键        // 会判断当前线程是不是属于同步队列的头节点的下一个节点(头节点是释放锁的节点)        // 如果是(返回false),符合先进先出的原则,可以获得锁        // 如果不是(返回true),则继续等待        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;}

    代码和 Sync 的 nonfairTryAcquire 方法实现类似,唯一不同的是在获得锁时使用 hasQueuedPredecessors 方法体现了其公平性。

    6、NonfairSync 非公平锁

    NonfairSync 底层实现了 lock 和 tryAcquire 两个方法,如下:

    // 加锁final void lock() {    // cas 给 state 赋值    if (compareAndSetState(0, 1))        // cas 赋值成功,代表拿到当前锁,记录拿到锁的线程        setExclusiveOwnerThread(Thread.currentThread());    else        // acquire 是抽象类AQS的方法,        // 会再次尝试获得锁,失败会进入到同步队列中        acquire(1);}// 直接使用的是 Sync.nonfairTryAcquire 方法 protected final boolean tryAcquire(int acquires) {    return nonfairTryAcquire(acquires);}

    7、如何串起来

    以上内容主要说了 ReentrantLock 的基本结构,比较零散,那么这些零散的结构如何串联起来呢?我们是通过 lock、tryLock、unlock 这三个 API 将以上几个类串联起来,我们来一一看下。

    7.1 lock 加锁

    lock 的代码实现:

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

    其底层的调用关系(只是简单表明调用关系,并不是完整分支图)如下:

    java底层组合AQS实现类kReentrantLock源码分析

    7.2 tryLock 尝试加锁 

     tryLock 有两个方法,一种是无参的,一种提供了超时时间的入参,两种内部是不同的实现机制,代码分别如下:

    // 无参构造器public boolean tryLock() {    return sync.nonfairTryAcquire(1);}// timeout 为超时的时间,在时间内,仍没有得到锁,会返回 falsepublic boolean tryLock(long timeout, TimeUnit unit)        throws InterruptedException {    return sync.tryAcquireNanos(1, unit.toNanos(timeout));}

    接着我们一起看下两个 tryLock 的调用关系图,下图显示的是无参 tryLock 的调用关系图,如下:

    java底层组合AQS实现类kReentrantLock源码分析

    我们需要注意的是 tryLock 无参方法底层走的就是非公平锁实现,没有公平锁的实现。

    下图展示的是带有超时时间的有参 tryLock 的调用实现图:

    java底层组合AQS实现类kReentrantLock源码分析

    7.3、unlock 释放锁 

    unlock 释放锁的方法,底层调用的是 Sync 同步器的 release 方法,release 是 AQS 的方法,分成两步:

    尝试释放锁,如果释放失败,直接返回 false;释放成功,从同步队列的头节点的下一个节点开始唤醒,让其去竞争锁。

    第一步就是我们上文中 Sync 的 tryRelease 方法(4.1),第二步 AQS 已经实现了。

    unLock 的源码如下:

    // 释放锁public void unlock() {    sync.release(1);}

    7.4、Condition

    ReentrantLock 对 Condition 并没有改造,直接使用 AQS 的 ConditionObject 即可。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发 Java
    相关文章 更多
    Java单元测试入门:使用JUnit 5编写第一个测试用例
    Java单元测试入门:使用JUnit 5编写第一个测试用例

    面向初学者的JUnit 5实用入门指南。从测试概念、Maven依赖配置,到手把手编写首个单元测试用例及断言验证,助你掌握Java单元测试的核心流程。

    Java变量未初始化报错解析:局部变量与成员变量默认值差异
    Java变量未初始化报错解析:局部变量与成员变量默认值差异

    深入解析Java中局部变量未初始化编译报错与成员变量自动赋予默认值的机制差异,涵盖JVM内存分配、编译期确定性赋值检查与实际排查修复方案。

    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat
    Java获取当前时间并格式化输出:DateTimeFormatter与SimpleDateFormat

    详细讲解在Java中获取当前系统时间并进行格式化输出的两种核心方案:基于Java 8全新日期时间API的DateTimeFormatter与传统遗留的SimpleDateFormat,包含完整代码示例、线程安全机制对比及生产选型建议。

    Java中final与static final常量的定义及初始化规则
    Java中final与static final常量的定义及初始化规则

    深入解析Java中final变量与static final常量的定义、内存语义、初始化时机及合法赋值路径,涵盖编译期常量优化与实战避坑指南。

    Java抽象方法与接口方法的区别及选型建议
    Java抽象方法与接口方法的区别及选型建议

    深入对比Java中抽象类抽象方法与接口方法的语法演进、设计意图及运行期差异,并提供清晰的架构选型决策流程与实战范例。

    Java实现HTTP请求与JSON解析的完整代码示例
    Java实现HTTP请求与JSON解析的完整代码示例

    本文提供Java标准库HttpClient发起HTTP GET/POST请求并结合Jackson进行JSON数据反序列化与序列化的完整代码示例与工程实践指南。

    Java递归出现StackOverflowError的原因分析及栈空间优化方案
    Java递归出现StackOverflowError的原因分析及栈空间优化方案

    详细分析Java递归调用出现StackOverflowError的原因,结合代码说明线程栈、栈帧和递归深度之间的关系,并介绍终止条件排查、迭代改写、减少栈帧开销以及通过-Xss调整JVM栈空间的方法。

    Java空指针异常(NPE)的代码排查与修复实例
    Java空指针异常(NPE)的代码排查与修复实例

    针对Java中常见的NullPointerException(NPE),本文从堆栈日志定位、典型代码触发场景、具体修复代码对比到防御体系进行系统讲解,提供实用且易上手的排查与修复方案。

    selenium上传文件教程本地文件路径设置方法
    selenium上传文件教程本地文件路径设置方法

    深入解析Selenium处理文件上传的技术细节,涵盖标准HTML控件的路径直接注入法与非标准控件的系统级模拟方案,提供Windows与Linux环境下的路径设置最佳实践及常见异常排查。

    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景
    大数据开发语言选型:Java对比Scala与Python的优缺点及适用场景

    针对大数据工程场景,系统对比Java、Scala与Python在执行性能、生态结合、开发效率和维护成本上的核心差异,给出清晰的架构选型依据与落地方案。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。