ReentrantLock 公平锁源码 第2篇

Reentrant 2

前两篇写完了后我自己研究了下,还有有很多疑惑和问题,这篇就继续以自问自答的方式写

如果没看过第1篇的可以先看看那个https://www.cnblogs.com/sunankang/p/16458795.html

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

进入 acquireQueued方法

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

第一个问题

interrupted这个变量的作用

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

parkAndCheckInterrupt方法中最后return的是这个线程是否被打断,它的作用是啥?

先来回顾 interrupt()interrupted()isInterrupted()三者区别,长得很像,注意区分

interrupt()的作用是中断线程,如果被中断的线程处于阻塞状态下,例如调用 wait(), join() sleep(),则抛出异常,否则只是设置一个中断标记为true, 注意:仅仅是设置中断状态为true,并不会去 “中断” 线程

interrupted() 获取线程的中断状态并且清空中断状态(将中断状态设置为false)

isInterrupted() 获取线程的中断状态并不会清除中断状态

调用 interrupt 会使park方法立即结束,可以理解为唤醒

继续代码,看这个变量最后到了哪里

情况1 没有被打断过

假设线程没有被中断过,那么 parkAndCheckInterrupt返回就是false

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

那么不进入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())这个if,获取到锁后返回false,回到 acquire方法

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

因为false,所以不进入 selfInterrupt(),方法结束

情况2 park或准备park,被唤醒后直接获取到了锁

先证明一下打断是会唤醒park中的线程的

 ReentrantLock 公平锁源码 第2篇

我就再重复粘一下代码了,方便看

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

那么返回的就是true,回到上级 acquireQueued方法

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

因为返回true,所以进入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 将interrupted返回true

假设循环获取到锁,那么再返回上一级 acquire()

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

那么进入 selfInterrupt()

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

是不是有点疑惑?我如果没有调用过 interrupt() 那ReentrantLock就不做任何操作,我如果调用了,那它再给我调用一次 ???? 还有情况3

情况3 park或准备park,被唤醒后没有获取到锁

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;
            }
            //假设在调用shouldParkAfterFailedAcquire成功后,马上就要调用parkAndCheckInterrupt 时间片用完了
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

那么这个时候interrupted属性就有用了

首先要知道一点, 一个被中断的线程是无法park的,除非清除了中断状态,即设置为将中断状态设置为false, 口说无凭,直接上图

 ReentrantLock 公平锁源码 第2篇

 ReentrantLock 公平锁源码 第2篇

第二张图还是在park状态,证明了被打断的线程是无法park的,除非将它中断状态设置为false

那么回到代码中就能知道这个的作用

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())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

如果线程被打断唤醒,还是在 for(;;)中,还是去获取锁,假设没有获取到呢?那么就一直在for循环中嘎嘎跑,因为线程的状态是被中断的,无法再次park了

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

那么现在懂了最后的 Thread.interrupted()作用了吗,就是将中断状态设置回false,好让线程没有获取到锁继续park

那这时候可能就问了:那你ReentrantLock把中断状态给我清空了,我自己如果有需要根据中断状态来判断的代码咋办啊?

好,咱们从park先被打断来捋一下

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

因为被打断,线程醒来,执行 Thread.interrupted()并清空中断状态,返回true

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())
                 //进入这里
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

因为返回的是true,所以进入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())的代码块,将 interrupted属性设置为true

那么 for(;;)循环再来一次,如果没有获取到锁.继续park,直到被唤醒,走 tryAcquire()获取到为止,那么此时 interrupted变量就为true了

public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

那么退出 acquireQueued()方法回到 acquire()中,因为 acquireQueued()返回的是true,所以进入 selfInterrupt()

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

所以懂了吗?

Original: https://www.cnblogs.com/sunankang/p/16461261.html
Author: Jame!
Title: ReentrantLock 公平锁源码 第2篇

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

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

(0)

大家都在看

  • Centos 7.x nginx隐藏版本号

    一、打开配置文件 #vim /etc/nginx/nginx.conf 二、增加一行: server_tokens off; 三、重启nginx #nginx -s reload …

    Java 2023年5月30日
    0160
  • Spring Boot 使用 Log4j2

    Java 中比较常用的日志工具类,有 Log4j、SLF4j、Commons-logging(简称jcl)、Logback、Log4j2(Log4j 升级版)、Jdk Loggin…

    Java 2023年5月30日
    094
  • 大批量数据的插入优化的小细节

    今天在测试将elasticsearch中的20万条数据迁移到MySQL中时,总感觉速度比较慢,尝试了好多方法,比如网上都说的: public void batchSave(List…

    Java 2023年6月7日
    087
  • Home Assistant接入HomeKit与简要介绍

    在HomeAssistant中接入HomeKit后可以使用iPad或iPhone的家庭App控制已接入HA的设备,如HA接入了很多小米智能家居设备,HA接入HomeKit后就可以使…

    Java 2023年6月16日
    0101
  • 来自Java程序员的Python新手入门小结

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kuberne…

    Java 2023年6月8日
    0114
  • 医疗知识图谱的构建和应用

    医疗知识图谱是实现智慧医疗的基石,有望带来更高效精准的医疗服务;然而,现有知识图谱构建技术在医学领域中普遍存在效率低、限制多、拓展性差等问题。 知识应用 1. 语义全文检索 基于知…

    Java 2023年5月29日
    067
  • 工厂模式–摆脱你日复一日new对象却依旧单身的苦恼!

    前言 每每谈及到Java,就不免会想到一个悲伤的事实:你是否每天都在new对象,却依然坚守在单身岗上屹立不倒。(所谓面向对象编程的”缺点”hhh),这篇来学…

    Java 2023年6月5日
    085
  • android多文件上传,java服务端接收

    Android多文件上传,java服务端接收 1、Android端 代码: String uploadUrl = "http://xxx/uploadFiles&quot…

    Java 2023年6月5日
    084
  • 55.不舍

    dsfds posted @2022-09-28 08:31 随遇而安== 阅读(5 ) 评论() 编辑 Original: https://www.cnblogs.com/55z…

    Java 2023年6月7日
    082
  • MySQL的主从复制和分库分表初探

    主从复制 + 分库分表 要讲主从复制,首先来看看MySQL自带的日志文件。 日志 错误日志 错误日志是 MySQL 中最重要的日志之一,它记录了当 mysqld 启动和停止时,以及…

    Java 2023年6月15日
    082
  • Java全家桶的这些知识,不用学了

    众所周知,Java 的知识体系繁冗复杂,但是有很多知识在实际工作中几乎没有人用。 很多人在学习过程中,却经常把有限的时间和精力花在了这些” 没有用“的知识上…

    Java 2023年6月7日
    069
  • 日常踩坑_SpringBoot项目Controller层同时传输MultipartFile和实体类

    背景提要 需求是一个表单,需要同时上传附件和一些其他内容(例如标题、内容之类的),本身是把 MultipartFile作为一个数组直接放入创建的对象 FileSaveDTO中的,但…

    Java 2023年6月7日
    080
  • Mysql的基础知识

    基础知识 多版本并发控制MVCC MVCC是怎么提高并发能力 MVCC的实现 快照读和当前读 快照读 当前读 Update的执行过程 聚簇索引和辅助索引 事务隔离级别 基础知识 多…

    Java 2023年6月9日
    0273
  • windows的命令行直接打开程序

    右键我的电脑 》 属性 》 高级设置 》 环境变量 》 双击 系统变量里 的 path 》 新建一个路径就好 被打开的文件的 存储目录 就是 路径。 比如: gvim 编辑器, 存…

    Java 2023年6月13日
    083
  • linux 映射windows 下的共享文件夹

    linux 映射windows 下的共享文件夹 本文讯】2021年4月27日 在对接第三方系统,进行数据采集的时候,对方给了我们一个文件夹,里面全是txt文件,这个时候就要想办法获…

    Java 2023年6月16日
    087
  • SpringBoot 源码解析 (二)—– Spring Boot精髓:启动流程源码分析

    本文从源代码的角度来看看Spring Boot的启动过程到底是怎么样的,为何以往纷繁复杂的配置到如今可以这么简便。 入口类 @SpringBootApplication publi…

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