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)

大家都在看

  • pg数据库匹配正则

    select ‘41142619960609331x’ ~ ‘^[1-9]\d{5}\d{4}((0[1-9])|(10|11|12))(([0…

    数据库 2023年6月16日
    084
  • 【MySQL】笔记(3)— 连接查询;子查询;union;limit;

    一.连接查询: 1.1、什么是连接查询?在实际开发中,大多数情况下并不是从单个表中查询数据,而是通常通过多个表的联合查询来获得最终结果。 [En] In the actual de…

    数据库 2023年5月24日
    074
  • macOS快捷键

    1. 最小化所有应用程序 command+option+h+m 2. 同应用窗口切换 command ~ 3. 截图 "全&a…

    数据库 2023年6月14日
    070
  • Redis-数据结构

    Redis key-value结构组织 首先,Redis使用了一个全局哈希表来保存所有的键值对。这个全局哈希表,也就是一个存放哈希桶(entry)的数组。Redis可以用哈希算法算…

    数据库 2023年6月11日
    077
  • 【黄啊码】MySQL入门—1、SQL 的执行流程

    大家好!我是黄啊码,鉴于大家对于学习的热情,从今天起,将连载mysql的相关知识,需要学习的可以注意我的更新学习,后期估计会开启付费专栏,但当前完全可以白嫖,希望大家珍惜! 首先我…

    数据库 2023年6月16日
    076
  • 用 WebClient 代替 RestTemplate

    RestTemplate是用于执行 HTTP 请求的同步客户端,通过底层 HTTP 客户端库(例如 JDK HttpURLConnection、Apache HttpCompone…

    数据库 2023年6月14日
    067
  • Mybatis-Spring源码分析

    Mybatis-Spring 博主技术有限,本文难免有错误的地方,如果您发现了欢迎评论私信指出,谢谢JAVA技术交流群:737698533 当我们使用mybatis和spring整…

    数据库 2023年6月16日
    091
  • 【数据结构】跳表

    一、基本概念 1.1 定义 跳表(SkipList):增加了向前指针的链表叫做指针。跳表全称叫做跳跃表,简称跳表。跳表是一个随机化的数据结构,实质是一种可以进行二分查找的有序链表。…

    数据库 2023年6月11日
    088
  • LeetCode 28. 实现strStr()

    实现strStr()函数。 给你两个字符串haystack和needle,请你在haystack字符串中找出needle字符串出现的第一个位置(下标从0开始)。如果不存在,则返回-…

    数据库 2023年6月11日
    069
  • MySQL 事务和锁

    事务概述 当多个用户访问相同的数据时,在更改数据的过程中,其他用户可能会同时发起更改请求,为了确保数据库记录的更新从一种一致性状态更改为另一种一致性状态,需要使用事务处理,它具有以…

    数据库 2023年5月24日
    061
  • Redis-内存淘汰策略

    策略分类 内存写满了怎么办?Redis提供了以下几种内存淘汰的策略: No eviction 不淘汰数据 即,内存写满后,再有写请求时,Redis直接返回错误,不会提供服务。这也是…

    数据库 2023年6月11日
    080
  • 【必知必会】手把手教你配置MySQL环境变量——图文详解

    一、先决条件 假设我们已经成功安装MySQL数据库。如果还有小伙伴不知道如何安装MySQL数据库,可以在本文下留言,留言数超20,则出一期”手把手教你安装MySQL数据…

    数据库 2023年6月14日
    053
  • 我的JAVA面试题备忘录

    以下是我收集的一些问题,有的是网上摘录的,有的是自己参加面试被问到的,有的是工作或学习时遇到的,等等。 为什么要记录这些呢? 一方面,我相信,这样做对我自己的技术提升是有帮助的。在…

    数据库 2023年6月6日
    052
  • 深入浅出分析 HashMap

    作者:炸鸡可乐原文出处:www.pzblog.cn 一、摘要 在集合系列的第一章,咱们了解到,Map的实现类有HashMap、LinkedHashMap、TreeMap、Ident…

    数据库 2023年6月14日
    068
  • centos8安装mysql

    前言 最近在centos8系统下部署django项目时,要用到mysql数据库,在安装中遇到了点坑,之后参考了一位博主的文章,也是顺利的安装配置成功,博主原文连接: ((20条消息…

    数据库 2023年5月24日
    081
  • Java8-Stream流

    Java8-Stream基础操作 JAVA技术交流群:737698533 在学习Stream之前必须有Lambda,的基础 Stream是Java8的新特性,可以进行对集合进行一些…

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