Java并发之同步三:Condition条件队列

一、总览

在这里插入图片描述

二、源码分析

2.1 人口

  public Condition newCondition() {
        return sync.newCondition();
    }

 final ConditionObject newCondition() {
            return new ConditionObject();
   }
 public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        // 指向条件队列的第一个node节点
        private transient Node firstWaiter;
        // 指向条件队列的最后一个node节点
        private transient Node lastWaiter;

        /**
         * Creates a new {@code ConditionObject} instance.
         */
        public ConditionObject() { }
 }

await方法:

    public final void await() throws InterruptedException {
            // 判断当前线程是否是中断
            if (Thread.interrupted())
                throw new InterruptedException();
            // 将调用await方法的线程包装成node并加入条件队列中,并返回当前node
            Node node = addConditionWaiter();
            // 完全释放当前线程对应的锁,
            // 为什么要释放锁那?加着锁挂起后,谁还能救你那
            int savedState = fullyRelease(node);
            // 0 在condition队列挂起期间未接收中断信号
            // -1 在condition队列期间接收到中断信号
            // 1 在condition队列挂起期间未接收到中断信号,但是在迁移到“阻塞队列”过程中,接收过中断信号
            int interruptMode = 0;
            
            // isOnSyncQueue返回true表示当前线程对应的node已经迁移到“阻塞队列”
            // false 说明当前node 仍然还在条件队列中,需要继续park
            while (!isOnSyncQueue(node)) {
                // 挂起当前node对应的线程,接下去去看看signal过程
                LockSupport.park(this);
                
                // 什么时候会被唤醒?都有哪几种情况
                // 1、常规路径:外部线程获取到锁之后,调用signal()方法 转移条件队列的头节点到阻塞队列,当
                //   这个节点获取到锁后,会唤醒
                
                // 2.转移至阻塞至阻塞队列后,发现阻塞队列中的前驱节点状态是 取消状态,此时会唤醒当前节点
                // 3.当前节点挂起期间时,被外部线程使用中断唤醒
                
                // checkInterruptWhileWaiting: 就算在condition队列挂起期间,线程发生中断了,
                // 对应的node也会被迁移到“阻塞队列”
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // acquiredQueued:竞争队列的逻辑
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            // 考虑下 node.nextWaiter != null 条件什么时候成立那
            // 其实是node在条件队列内时,如果被外部线程中断唤醒时,会加入到阻塞队列,
            // 但是并未设置nextWaiter != null
            if (node.nextWaiter != null) // clean up if cancelled
                // 清理条件队列取消状态的节点
                unlinkCancelledWaiters();
            // 条件成立:说明挂起期间 发生过中断(1.条件队列内的挂起 2.条件队列之外的挂起)
            if (interruptMode != 0)
                // 条件队列内发生中断,此外await方法抛出中断异常
                // 条件队列外发生的中断,交给你的业务处理,如果你不处理,那什么事也不会发生
                reportInterruptAfterWait(interruptMode);
        }
  1. 将当前线程包装成ConditionNode加入到条件队列中
  2. 释放锁资源
  3. while循环,如果当前线程对应的node已经迁移到“阻塞队列”,那就park挂起它,然后开始走acquireQueued逻辑,开始竞争锁 (这里线程什么时候会到“阻塞队列”中那)
private int checkInterruptWhileWaiting(Node node) {
            // thread.interrupted 返回当前线程中断标记位,并且重置当前标记为false
            return Thread.interrupted() ?
                // transferAfterCancel 这个方法只有在线程是被中断唤醒时才会调用
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

 final boolean transferAfterCancelledWait(Node node) {
        // 条件成立:说明当前node一定是在条件队列内,因为signal迁移节点
        // 到阻塞队列时,会将节点的状态修改为0
        if (node.compareAndSetWaitStatus(Node.CONDITION, 0)) {
            // 中断唤醒的node也会被加入到阻塞队列中
            enq(node);
            // true:表示是在条件队列内被中断的
            return true;
        }
       
        // 执行到这里有几种情况?
        // 1、当前node已经被外部线程调用 signal方法将其迁移到阻塞队列内
        // 2、当前node正在被外部线程调用 signal方法将其迁移到阻塞队列中
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

signal方法

 public final void signal() {
            // 判断调用signal方法的线程是否是独占锁持有线程,如果不是,直接抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
    private void doSignal(Node first) {
            do {
                // firstWaiter = first.nextWaiter 因为当前first马上要出条件队列
                // 如果当前节点的下一个节点是NULL,说明条件队列只有当前一个节点了。。
                // 当前出队后,整个队列就空了
                // 所以需要更新lastWaiter = null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 当前first节点 出条件队列,断开和下一个节点的关系
                first.nextWaiter = null;
                
                // transferForSignal(first)
                // true:当前first节点迁移到阻塞队列成功 false迁移失败
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
  final boolean transferForSignal(Node node) {
        // cas 修改当前节点的状态,修改为0,因为当前节点马上要迁移到 阻塞队列中
        // 成功:当前节点在条件队列中状态正常
        // 失败: 1、取消状态(线程await时,未持有锁,最终线程对应的node会设置为取消状态)
        //       2、node对应的线程挂起期间,被其他线程使用中断信号 
                   唤醒过(就会主动进入到阻塞队列中),这时候也会修改状态为0
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
            return false;

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
        Node p = enq(node);
        int ws = p.waitStatus;
        // 条件一:ws >0 成立:说明前驱节点状态在阻塞队列中是取消状态,唤醒当前节点
        // 条件二: 前置条件(ws <= 0) 
        //  compareAndSetWaitStatus(p,ws,Node.SIGNAL)表示设置前驱节点状态 SIGANAL状态成功
        //  compareAndSetWaitStatus(p,ws,NOde.SIGNAL)返回false ==> 什么时候返回false
        //  当前驱node对应的线程是lockInterrupt 入队的node时,是会响应中断的,外部线程给前驱线程中断
        //  信号之后,前驱node状态修改为取消状态,并且执行出队逻辑
        //  前驱节点状态只要不是0 或者-1,那么就唤醒当前线程
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

细说DMD芯片信号-DLP3

1&#xff0c; Block diagram 2. 信号介绍 2.1, LS interface&#xff1a; LD_Data_P/N(i), LD_CLK_P/N(i), LS_RDATA_A_BIST(O) 2.2, 视频信号: HSSI(High speed serial interface) High speed Differential Data pair lan A0~7 P/N, High speed Differential Clock A High…

《Vue2 进阶知识》动态挂载组件之Vue.extend + vm.$mount

前言 目前工作还是以 Vue2 为主&#xff0c;今早有人提问 如何动态挂载组件&#xff1f; 话说很久很久以前就实现过&#xff0c;今天再详细的整理一下此问题&#xff01; 开始 动态组件如下&#xff0c;是个简单的例子&#xff1a; 但请注意这里给了个 id"test2"…

CloudCompare——点云空间圆拟合

目录 1.概述2.软件实现3.完整操作4.算法源码5.相关代码 本文由CSDN点云侠原创&#xff0c;CloudCompare——点云空间圆拟合&#xff0c;爬虫自重。如果你不是在点云侠的博客中看到该文章&#xff0c;那么此处便是不要脸的爬虫与GPT生成的文章。 1.概述 CloudCompare软件中的To…

【Java反射】Java利用反射获取和设置对象某属性的值

通用工具类&#xff1a; package com.zlp.util;import com.fasterxml.jackson.annotation.JsonProperty;import java.lang.reflect.Field;public class ReflectUtil {/*** 反射获取对象的属性值** param object 对象&#xff08;要遍历的对象&#xff09;* param targetFieldN…

QT上位机开发(日志调试)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 程序开发中有很多的调试方法&#xff0c;比如说IDE调试&#xff0c;也就是设置断点、查看变量等等&#xff1b;比如说日志调试&#xff1b;比如说c…

Vue2 实现带输入的动态表格,限制el-input输入位数以及输入规则(负数、小数、整数)

Vue2 实现el-input带输入限制的动态表格&#xff0c;限制输入位数以及输入规则&#xff08;负数、小数、整数&#xff09; 在这个 Vue2 项目中&#xff0c;我们实现一个限制输入位数&#xff08;整数16位&#xff0c;小数10位&#xff09;以及输入规则&#xff08;负数、小数、…

Python商业数据挖掘实战——爬取网页并将其转为Markdown

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 ChatGPT体验地址 文章目录 前言前言正则表达式进行转换送书活动 前言 在信息爆炸的时代&#xff0c;互联网上的海量文字信息如同无尽的沙滩。然而&#xff0c;其中真正有价值的信息往往埋…

如何高效编写测试用例

本话题暂不探讨是否有必要编写详细的测试用例&#xff0c;在确定要交付详细的测试用例这个前提下&#xff0c;分享如何更高效地完成测试用例的编写。 对齐测试用例需求 首先、明确要完成的测试用例文档目标要求&#xff0c;模板、范围、粒度等。 用例文档使用者&#xff1a;…

内网穿透NPS搭建以及使用

今天说一下 内网穿透代理&#xff08;NPS&#xff09;搭建以及使用&#xff0c;内网穿透必然有一个外网服务器做代理转发&#xff0c;市面上的NATAPP、花生壳等也都是一个原理。 需求&#xff1a; window 本地开发&#xff0c;外网访问本地服务联合调试 环境&#xff1a; 公网…

HackerGPTWhiteRabbitNeo的使用及体验对比

1. 简介 WhiteRabbitNeo&#xff08;https://www.whiterabbitneo.com/&#xff09;是基于Meta的LLaMA 2模型进行特化的网络安全AI模型。通过专门的数据训练&#xff0c;它在理解和生成网络安全相关内容方面具有深入的专业能力&#xff0c;可广泛应用于教育、专业培训和安全研究…

基于python的室内老人实时摔倒智能监测系统(康复训练检测+代码)

概述 导入所需的库&#xff0c;包括cv2、和numpy。 定义了一个用于计算角度的函数calculate_angle(a, b, c)&#xff0c;其中a、b和c是三个关键点的坐标。 初始化姿态检测和绘图工具。 打开并读取视频文件。 -摔倒检测&#xff08;fallen&#xff09; 循环遍历视频的每一帧…

【计算机组成-计算机基本结构】

课程链接&#xff1a;北京大学陆俊林老师的计算机组成原理课 1. 电子计算机的兴起 原因&#xff1a;二战对计算能力的需求世界上第一台通用电子计算机 ENIAC&#xff08;Electronic Numerical Integrator And Computer&#xff09;&#xff1a;时间&#xff1a;1946&#xff1…

小智ToDo:日程待办清单管理的智能助手

在繁忙的工作与生活中&#xff0c;有效的时间管理和任务规划是提高效率的关键。今天&#xff0c;我们来探讨一款名为“小智ToDo”的日程待办清单管理工具&#xff0c;它以其多端数据同步、备忘提醒、日程管理等实用功能&#xff0c;为用户提供了便捷的时间管理解决方案。 小智T…

单主机双屏幕实现跨屏幕信息交互的GUI程序

单主机双屏幕实现跨屏幕信息交互的GUI程序 运行程序界面 屏幕1发送数据&#xff0c;屏幕2接收数据 在屏幕1按下打开开关&#xff0c;屏幕2播放视频 代码程序 import tkinter as tk # 导入tkinter模块&#xff0c;用于创建GUI界面 import threading # 导入threading模块&#…

【Java 干货教程】Java实现分页的几种方式详解

一、前言 无论是自我学习中&#xff0c;还是在工作中&#xff0c;固然会遇到与前端搭配实现分页的功能&#xff0c;发现有几种方式&#xff0c;特此记录一下。 二、实现方式 2.1、分页功能直接交给前端实现 这种情况也是有的&#xff0c;(根据业务场景且仅仅只能用于数据量…

RT-Thread GD32F4xx实现SD卡热插拔检测功能

GD32F470移植RT-Thread操作系统添加SD卡功能&#xff0c;增加SD卡热插拔检测 一、RT-Thread移植sd卡功能二、实现SD卡热插拔检测原理三、软件实现过程四、延展之ASSERT ERROR&#xff0c;即RT-Thread断言错误五、延展之STM32 SD卡热插拔检测六、结束语 一、RT-Thread移植sd卡功…

代码随想录算法训练营第三十一天|理论基础、455.分发饼干、376. 摆动序列、53. 最大子序和

题目&#xff1a;理论基础 解释&#xff1a;贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优 题目&#xff1a;455.分发饼干 文章链接&#xff1a;代码随想录 视频链接&#xff1a;LeetCode:455.分发饼干 题目链接&#xff1a;力扣题目链接 图释&#x…

C语言用函数指针实现计算器

一、运行结果&#xff1b; 二、源代码&#xff1b; # define _CRT_SECURE_NO_WARNINGS # include <stdio.h>//实现目录函数&#xff1b; void menum() {//打印目录&#xff1b;printf("***********************************************\n");printf("***…

公司官网,选全站定制还是模板建站?

最近更新了公司网站&#xff0c;总算了了一件大事。 虽然很久以前也做网站&#xff0c;但随着技术的发展&#xff0c;以前经常用的dreamwaver、table等形式&#xff0c;不知道被升级了多少代。现在前端同事说起的各种架构&#xff0c;对我来说是云里雾里。只能看懂一点点。 这…

【cmu15445c++入门】(5)c++中的模板类

一、template模板类 除了模板方法【cmu15445c入门】(4)c中的模板方法 模板也可以用来实现类 二、代码 /*** file templated_classes.cpp* author Abigale Kim (abigalek)* brief Tutorial code for templated classes.*/// Includes std::cout (printing). #include <io…