ReentrantLock可重入、可打断、Condition原理剖析

本文紧接上文的AQS源码,如果对于ReentrantLock没有基础可以先阅读我的上一篇文章学习ReentrantLock的源码

重入加锁其实就是将AQS的state进行加一操作

然后释放锁资源将AQS的state进行减一操作

当state为0时才会彻底的释放锁资源

在ReentrantLock中可打断就是在等待锁的过程中可以被interrupt打断(需要调用lockInterruptibly),lock方法设置了打断标记,但是只有在线程获得锁的时候才能知道自己有没有在阻塞的过程中有没有被打断。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted; // 返回打断标记
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())// 我们这边会检查打断,如果打断的话返Thread.interrupted()
                interrupted = true;  // 这里将打断标记置为true后,继续进入循环。直到获得到锁返回标记
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

首先我们需要调用加锁的lockInterruptibly()方法

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

可打断主要原因在如下代码解释。用异常代替了返回标记,让线程可以直接再park的过程中直接结束

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();  // 再被打断的时候不会将其标记置为true,而是直接抛出一个异常,打断当前的等待。
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

Condition是一个接口,实际上是ReentrantLock的ConditionObject类作为其实现类。

首先我们创建一个condition就会调用其构造方法。其实就是产生一个新的conditionObject

final ConditionObject newCondition() {
    return new ConditionObject();
}

接下来,简单剖析一个源码

  • 首先我们得知道,ConditionObject中维护着一个等待的双向链表,其实和阻塞链表是很相似的,不同在于不需要前驱进行唤醒。然后在ConditionObject中维护头和尾的引用就是firstWaiter和lastWaiter成员变量。
public final void await() throws InterruptedException {
    if (Thread.interrupted())   // 如果被打断,抛异常
        throw new InterruptedException();
    Node node = addConditionWaiter();  // 1.向AQS中添加一个等待链表的node
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) { // 是否在阻塞链表中
        LockSupport.park(this); // 不是的话直接进行阻塞起来
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)// 被打断了就得把它放阻塞链表
            break;
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)  // 多线程的遗留状态处理
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.

    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters(); // 这个和阻塞队列相似不过是全部遍历,清除不在等待队列上的node
        t = lastWaiter;
    }
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)              // 添加到waitting尾部
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

signal唤醒源码,简单介绍就是直接将等待的firstWaiter指向的等待链表的第一个进行解除阻塞,然后将其放入阻塞链表中。不过多赘述。

public final void signal() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

Original: https://www.cnblogs.com/duizhangz/p/16267995.html
Author: 大队长11
Title: ReentrantLock可重入、可打断、Condition原理剖析

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/599321/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Java8Stream流

    Stream流呢,以前我也有所了解,像一些面试题中也出现过,Java8的新特性,有一块就是这个Stream操作集合,而且在看一些项目中也使用的比较多。但总感觉自己学的一知半解,所以…

    数据库 2023年6月11日
    0123
  • MySQL中如何选择合适的备份策略和备份工具

    ​数据库备份的重要性毋庸置疑,可以说,它是数据安全的最后一道防线。鉴于此,对于备份,我们通常会做以下要求: 多地部署 对于核心数据库,我们通常有两地三中心的部署要求。对于备份来说,…

    数据库 2023年6月11日
    0116
  • Python爬虫详解

    1、任务介绍 需求分析爬取豆瓣电影Top250的基本信息,包括电影的名称,豆瓣评分,评价数,电影概况,电影链接等。 https://movie.douban.com/top250 …

    数据库 2023年6月16日
    0163
  • MySQL隐式转换的坑

    MySQL以以下规则描述比较操作如何进行转换: 两个参数至少有一个是 NULL 时,比较的结果也是 NULL,例外是使用 实际使用中经研究发现字符串和数字比较时,优先把字符串转换为…

    数据库 2023年6月9日
    0110
  • 分享一例同一系统里不同服务之间通信的设计方案

    优付系统结构如下。一个数据库之上,有商户接口(RestAPI)、运营后台(OMS)、商户门户这3个独立SSM应用,三者有各自不同的功能处理逻辑。 现在呢,要做一个补偿工具。当付款单…

    数据库 2023年6月9日
    0170
  • BigDecimal 设置小数位数、小数比例转换整数

    控制小数位数 DecimalFormat decimalFormat = new DecimalFormat("0.00"); decimalFormat.fo…

    数据库 2023年6月6日
    0123
  • 设计模式在业务系统中的应用

    本文的重点在于说明工作中所使用的设计模式,为了能够更好的理解设计模式,首先简单介绍一下业务场景。使用设计模式,可以简化代码、提高扩展性、可维护性和复用性。有哪些设计模式,这里就不再…

    数据库 2023年6月14日
    0131
  • 线程池使用InheritableThreadLocal出现数据脏乱分析和解决方案

    背景 在测试环境上遇到一个诡异的问题,某些业务场景需要记录操作日志,有段时间发现记录的数据会被覆盖,例如当前用户的操作记录会被其他用户覆盖;这个现象是每次重启服务后一小段时间内就正…

    数据库 2023年6月6日
    0165
  • Spring Boot启动流程源码解析

    定义: BeanFactoryPostProcessor:是一个接口,它允许自定义修改应用程序上下文的beanDefiinition BeanDefinitionRegistryP…

    数据库 2023年6月16日
    0136
  • MySQL中的触发器

    1.定义: 触发器和存储过程相似,都是嵌入到 MySQL 中的一段程序。触发器是由事件来触发某个操作。当数据库执行这些事件时,就会激活触发器来执行相应的操作。这些事件称为触发条件,…

    数据库 2023年6月16日
    0154
  • Go 并发编程并非易事

    前言 Go 语言的一大卖点可以说是 并发编程。作为一门非常年轻的语言(诞生于2006年),在Google的培育下,为了充分利用多核机器资源的并发优势,从底层原生支持并发。 实现并发…

    数据库 2023年6月6日
    0122
  • Volatile的学习

    首先先介绍三个性质 可见性 可见性代表主内存中变量更新,线程中可以及时获得最新的值。 下面例子证明了线程中可见性的问题 由于发现多次执行都要到主内存中取变量,所以会将变量缓存到线程…

    数据库 2023年6月11日
    0116
  • MySQL进阶系列:一文详解explain

    explain有何用处呢: 为了知道优化SQL语句的执行,需要查看SQL语句的具体执行过程,以加快SQL语句的执行效率。 ​ 可以使用explain+SQL语句来模拟优化器执行SQ…

    数据库 2023年5月24日
    0168
  • 域名SSL认

    阿里云:域名SSL认证-视频 Hole yor life get everything if you never give up. Original: https://www.cn…

    数据库 2023年6月9日
    0157
  • Linux–>进程管理

    基本介绍 在Linux中, 每个执行程序都称为一个进程。每一个进程都会分配一个ID号(pid,进程号) 每个进程都可能以俩种方式存在的。分别是 前台与 后台,所谓前台进程就是用户目…

    数据库 2023年6月14日
    0151
  • MySQL学习(3)—MySQL常用命令

    ps:此随笔基于mysql 5.7.*版本。 准备 net start mysql 启动MySQL服务 net stop mysql 关闭MySQL服务 mysql [-h exi…

    数据库 2023年5月24日
    0124
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球