J.U.C - 深入解读Condition条件变量原理源码

文章目录

  • Pre
  • 概述
  • Condition 主要方法
  • Condition案例
  • Condition的源码解析
    • 1. 等待:condition. await
    • 2. 唤醒Condition. signal
  • Condition总结

在这里插入图片描述


Pre

J.U.C - 深入解析ReentrantLock原理&源码

概述

配合synchronized同步锁在同步代码块中调用加锁对象notify和wait方法,可以实现线程间同步,在JUC锁里面也有相似的实现:Condition类,利用条件变量Condition的signal和await方法用来配合JUC锁也可以实现多线程之间的同步。

和synchronized不同的是,一个synchronized锁只能用一个共享变量(锁对象)的notify或wait方法实现同步,而AQS的一个锁可以对应多个条件变量,对线程的等待、唤醒操作更加详细和灵活。

在调用加锁对象的notify和wait方法前必须先获取该共享变量的内置锁。同样,在调用条件变量的signal和await方法前也必须先获取条件变量对应的锁,因此Condition必须要配合锁一起使用,一个Condition的实例必须与一个Lock绑定,Condition一般都是作为Lock的内部实现。


Condition 主要方法

Condition提供了一系列的方法来阻塞和唤醒线程:

在这里插入图片描述

  • 1)await():造成当前线程在接到信号或被中断之前一直处于等待状态。
  • 2)await(long time,TimeUnit unit)​:当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
  • 3)awaitNanos(long nanosTimeout)​:当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值<= 0,则可以认定它已经超时。
  • 4)awaitUninterruptibly():当前线程在接到信号之前一直处于等待状态。注意:该方法对中断不敏感。
  • 5)awaitUntil(Date deadline)​:当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回false。
  • 6)signal():唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁。
  • 7)signalAll():唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁。

Condition案例

如何使用 ReentrantLockCondition 实现“等待”和“唤醒”的功能。创建了两个线程:Thread-awaitThread-signal,分别实现等待和唤醒的功能。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionExample {

    public static void main(String[] args) {
        // 创建一个重入独占锁
        Lock lock = new ReentrantLock();
        // 使用锁对象创建一个条件变量
        Condition condition = lock.newCondition();

        // 创建等待线程
        Thread awaitThread = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("Thread-await: 已获得锁,开始等待...");
                // 调用await方法,当前线程会释放锁并被阻塞等待
                condition.await();
                System.out.println("Thread-await: 被唤醒,继续执行...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("Thread-await: 释放锁");
            }
        }, "Thread-await");

        // 创建唤醒线程
        Thread signalThread = new Thread(() -> {
            lock.lock();
            try {
                // 模拟一些处理时间
                Thread.sleep(2000);
                System.out.println("Thread-signal: 已获得锁,唤醒等待线程...");
                // 调用signal方法,唤醒一个被阻塞的线程
                condition.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
                System.out.println("Thread-signal: 释放锁");
            }
        }, "Thread-signal");

        // 启动等待线程
        awaitThread.start();

        // 让等待线程先执行一段时间
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 启动唤醒线程
        signalThread.start();
    }
}
  1. 创建锁和条件变量

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    
  2. 创建等待线程 Thread-await

    • 获取锁。
    • 调用 condition.await() 方法,当前线程会释放锁并被阻塞等待。
    • 被唤醒后,继续执行并释放锁。
  3. 创建唤醒线程 Thread-signal

    • 获取锁。
    • 模拟一些处理时间(2秒)。
    • 调用 condition.signal() 方法,唤醒一个被阻塞的线程。
    • 释放锁。
  4. 启动线程

    • 先启动等待线程 Thread-await
    • 让等待线程先执行一段时间(1秒)。
    • 再启动唤醒线程 Thread-signal

运行过程

  1. 线程 Thread-await 获得锁并调用 await 方法

    • 输出:“Thread-await: 已获得锁,开始等待…”
    • 当前线程释放锁并被阻塞等待。
  2. 线程 Thread-signal 获得锁并调用 signal 方法

    • 模拟处理时间(2秒)。
    • 输出:“Thread-signal: 已获得锁,唤醒等待线程…”
    • 调用 condition.signal() 方法,唤醒被阻塞的 Thread-await 线程。
    • 释放锁。
  3. 线程 Thread-await 被唤醒并重新获得锁

    • 输出:“Thread-await: 被唤醒,继续执行…”
    • 继续执行并释放锁。
  4. 线程 Thread-await 释放锁

    • 输出:“Thread-await: 释放锁”

在这里插入图片描述


Condition的源码解析

在案例中,lock. newCondition()的作用其实是新建了 一 个ConditionObject对象,ConditionObject是AQS的内部类,可以访问AQS内部的变量(例如状态变量state)和方法。

在每个条件变量内部都维护了一个条件队列,用来存放调用条件变量的await()方法时被阻塞的线程

    /**
     * 返回一个 {@link Condition} 实例,用于与这个 {@link Lock} 实例一起使用。
     *
     * <p>返回的 {@link Condition} 实例支持与内置监视器锁一起使用的 {@link Object} 监视器方法({@link Object#wait() wait},
     * {@link Object#notify notify} 和 {@link Object#notifyAll notifyAll})相同的功能。
     *
     * <ul>
     *
     * <li>如果在调用 {@link Condition} 的任何 {@linkplain Condition#await() 等待} 或 {@linkplain Condition#signal 通知} 方法时未持有此锁,
     * 则会抛出 {@link IllegalMonitorStateException}。
     *
     * <li>当调用条件的 {@linkplain Condition#await() 等待} 方法时,锁会被释放,并且在这些方法返回之前,锁会被重新获取,锁的持有计数恢复到调用方法时的状态。
     *
     * <li>如果线程在等待时被 {@linkplain Thread#interrupt 中断},则等待将终止,抛出 {@link InterruptedException},并且线程的中断状态将被清除。
     *
     * <li>等待的线程按先进先出(FIFO)顺序被通知。
     *
     * <li>从等待方法返回的线程重新获取锁的顺序与最初获取锁的线程相同,默认情况下未指定,但对于 <em>公平</em> 锁,优先考虑等待时间最长的线程。
     *
     * </ul>
     *
     * @return 条件对象
     */
    public Condition newCondition() {
        return sync.newCondition();
    }


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

1. 等待:condition. await

调用Condition的await()系列方法,会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await()方法返回时,当前线程一定获取了Condition相关联的锁。

注意这个Condition队列和AQS队列有所区别,Condition的等待队列是一个单向队列

    /**
     * 实现可中断的条件等待。
     * <ol>
     * <li>如果当前线程被中断,抛出 InterruptedException。
     * <li>保存由 {@link #getState} 返回的锁状态。
     * <li>使用保存的锁状态作为参数调用 {@link #release},如果失败则抛出 IllegalMonitorStateException。
     * <li>阻塞直到被信号唤醒或被中断。
     * <li>通过调用带有保存的锁状态作为参数的 {@link #acquire} 的专门版本来重新获取锁。
     * <li>如果在第 4 步被阻塞时被中断,抛出 InterruptedException。
     * </ol>
     */
    public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException(); // 如果当前线程被中断,抛出 InterruptedException

        ConditionNode node = new ConditionNode(); // 创建一个新的条件节点
        int savedState = enableWait(node); // 保存锁状态
        LockSupport.setCurrentBlocker(this); // 设置当前阻塞者,用于向后兼容

        boolean interrupted = false, cancelled = false, rejected = false; // 初始化标志变量
        while (!canReacquire(node)) { // 循环直到可以重新获取锁
            if (interrupted |= Thread.interrupted()) { // 检查当前线程是否被中断
                if (cancelled = (node.getAndUnsetStatus(COND) & COND) != 0)
                    break; // 如果节点状态为 COND,则中断后退出循环
            } else if ((node.status & COND) != 0) { // 检查节点状态是否为 COND
                try {
                    if (rejected)
                        node.block(); // 如果被拒绝,调用 block 方法
                    else
                        ForkJoinPool.managedBlock(node); // 否则调用 managedBlock 方法
                } catch (RejectedExecutionException ex) {
                    rejected = true; // 标记为被拒绝
                } catch (InterruptedException ie) {
                    interrupted = true; // 标记为中断
                }
            } else
                Thread.onSpinWait(); // 如果在入队过程中被唤醒,进行自旋等待
        }

        LockSupport.setCurrentBlocker(null); // 清除当前阻塞者
        node.clearStatus(); // 清除节点状态
        acquire(node, savedState, false, false, false, 0L); // 重新获取锁

        if (interrupted) { // 如果线程被中断
            if (cancelled) {
                unlinkCancelledWaiters(node); // 移除已取消的等待者
                throw new InterruptedException(); // 抛出 InterruptedException
            }
            Thread.currentThread().interrupt(); // 重新设置线程的中断状态
        }
    }


2. 唤醒Condition. signal

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点)​,在唤醒节点之前,会将节点移到同步队列中

    /**
     * 将等待时间最长的线程(如果存在)从这个条件的等待队列移动到拥有锁的等待队列。
     *
     * @throws IllegalMonitorStateException 如果 {@link #isHeldExclusively} 返回 {@code false}
     */
    public final void signal() {
        ConditionNode first = firstWaiter; // 获取等待队列中的第一个节点
        if (!isHeldExclusively()) // 检查当前线程是否独占持有锁
            throw new IllegalMonitorStateException(); // 如果没有持有锁,抛出异常
        if (first != null) // 如果等待队列不为空
            doSignal(first, false); // 调用 doSignal 方法,将第一个节点从条件等待队列移动到锁的等待队列
    }

    /**
     * 移除并转移一个或所有等待者到同步队列。
     */
    private void doSignal(ConditionNode first, boolean all) {
        while (first != null) { // 遍历等待队列
            ConditionNode next = first.nextWaiter; // 获取下一个等待节点
            if ((firstWaiter = next) == null) // 更新 firstWaiter,如果 next 为 null,则 lastWaiter 也设为 null
                lastWaiter = null;
            if ((first.getAndUnsetStatus(COND) & COND) != 0) { // 检查节点状态是否为 COND
                enqueue(first); // 将节点从条件等待队列移动到同步队列
                if (!all) // 如果只需要唤醒一个等待者,则退出循环
                    break;
            }
            first = next; // 继续遍历下一个节点
        }
    }

Condition总结

阻塞:await()方法中,在线程释放锁资源之后,如果节点不在AQS等待队列,则阻塞当前线程,如果在等待队列,则自旋等待尝试获取锁。

释放:signal()后,节点会从condition队列移动到AQS等待队列,则进入正常锁的获取流程

在这里插入图片描述

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

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

相关文章

c++ 类和对象(中)

前言 我们看看下面的代码以及代码运行结果 代码1 我们可以看到在我们的类Data中的函数成员print中&#xff0c;我们并没有设置形参&#xff0c;在调用此函数时&#xff0c;也并没有多余传参&#xff0c;但是我们调用它时&#xff0c;却能准确打印出我们的_year、_month、_day…

python:用 sklearn 构建 K-Means 聚类模型

pip install scikit-learn 或者 直接用 Anaconda3 sklearn 提供了 preprocessing 数据预处理模块、cluster 聚类模型、manifold.TSNE 数据降维模块。 编写 test_sklearn_3.py 如下 # -*- coding: utf-8 -*- """ 使用 sklearn 构建 K-Means 聚类模型 "&…

使用python编写工具:快速生成chrome插件相关文件结构

本文将详细分析一段用 wxPython 编写的 Python 应用程序代码。该程序允许用户创建一些特定文件并将它们保存在指定的文件夹中&#xff0c;同时也能够启动 Google Chrome 浏览器并打开扩展页面&#xff0c;自动执行一些操作。 C:\pythoncode\new\crxiterationtaburl.py 全部代码…

使用Web Components构建模块化Web应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 使用Web Components构建模块化Web应用 使用Web Components构建模块化Web应用 使用Web Components构建模块化Web应用 引言 Web Co…

谷歌浏览器的自动翻译功能如何开启

在当今全球化的网络环境中&#xff0c;能够流畅地浏览不同语言的网页是至关重要的。谷歌浏览器&#xff08;Google Chrome&#xff09;提供了一项强大的自动翻译功能&#xff0c;可以帮助用户轻松跨越语言障碍。本文将详细介绍如何开启和使用谷歌浏览器的自动翻译功能&#xff…

算法---解决“汉诺塔”问题

# 初始化步骤计数器 i 1 # 定义移动盘子的函数 def move(n, mfrom, mto): global i # 使用全局变量i来跟踪步骤 print("第%d步:将%d号盘子从%s->%s" % (i, n, mfrom, mto)) # 打印移动步骤 i 1 # 步骤计数器加1 #第一种方法 # 定义汉诺塔问题的递归…

uniapp对接极光推送,实现消息推送功能

通过集成JG-JPush和JG-JCore插件&#xff0c;可以在应用中添加消息推送功能&#xff0c;向用户发送通知、消息等。这对于提升用户体验、增加用户粘性非常有帮助‌。 效果图&#xff1a; 一、登录极光官网 进入【服务中心】-【开发者平台】 创建应用&#xff1a;【概览】- 【创…

redis高性能键值数据库技术简介

什么是redis redis是远程字典服务&#xff08;Remote Dictionary Server &#xff09;的简写&#xff0c;是一个完全开源的高性能的Key-Value数据库&#xff0c;提供了丰富的数据结构如string、Hash、List、SetSortedset等等。数据是存在内存中的&#xff0c;同时Redis支持事务…

进程信号

目录 信号入门 1. 生活角度的信号 2. 技术应用角度的信号 3. 注意 4. 信号概念 5. 用kill -l命令可以察看系统定义的信号列表 6. 信号处理常见方式概览 产生信号 1. 通过终端按键产生信号 Core Dump 2. 调用系统函数向进程发信号 3. 由软件条件产生信号 4. 硬件异…

NotePad++中安装XML Tools插件

一、概述 作为开发人员&#xff0c;日常开发中大部的数据是标准的json格式&#xff0c;但是对于一些古老的应用&#xff0c;例如webservice接口&#xff0c;由于其响应结果是xml&#xff0c;那么我们拿到xml格式的数据后&#xff0c;常常会对其进行格式化&#xff0c;以便阅读。…

Java基础——多线程

1. 线程 是一个程序内部的一条执行流程程序中如果只有一条执行流程&#xff0c;那这个程序就是单线程的程序 2. 多线程 指从软硬件上实现的多条执行流程的技术&#xff08;多条线程由CPU负责调度执行&#xff09; 2.1. 如何创建多条线程 Java通过java.lang.Thread类的对象…

HarmonyOS ArkUI(基于ArkTS) 常用组件

一 Button 按钮 Button是按钮组件&#xff0c;通常用于响应用户的点击操作,可以加子组件 Button(我是button)Button(){Text(我是button)}type 按钮类型 Button有三种可选类型&#xff0c;分别为胶囊类型&#xff08;Capsule&#xff09;、圆形按钮&#xff08;Circle&#xf…

【FPGA开发】AXI-Stream总线协议解读

文章目录 AXI-Stream概述协议中一些定义字节定义流的定义 数据流类别字节流连续对齐流连续不对齐流稀疏流 协议的信号信号列表 文章为个人理解整理&#xff0c;如有错误&#xff0c;欢迎指正&#xff01; 参考文献 ARM官方手册 《IHI0051B》 AXI-Stream概述 协议中一些定义 A…

谷粒商城のMySQL集群分库分表

文章目录 前言一、MySQL的集群架构二、MySQL主从同步实践1.创建主节点实例2.创建从节点实例3.修改配置4.开始同步4.测试主从同步效果5.小结 三、MySQL分库分表1.配置sharding-proxy2.测试sharding-proxy3.小结 前言 本篇是谷粒商城集群部署篇&#xff0c;搭建MySQL集群以及分库…

计算机组成原理对于学习嵌入式开发的意义

计算机组成原理对于学习嵌入式开发的意义 前言 最近有位同学向我咨询&#xff0c;问学习嵌入式开发需不需要学习硬件&#xff1f;进而引申到了需不需要学习计算机组成原理呢&#xff1f; 正文 首先计算机组成原理是计算机科学与技术专业的一门核心基础课程&#xff0c;它深入…

npm list -g --depth=0(用来列出全局安装的所有 npm 软件包而不显示它们的依赖项)

您提供的命令 npm list -g --depth0 是在 Node Package Manager (npm) 的上下文中使用的&#xff0c;用来列出全局安装的所有 npm 软件包而不显示它们的依赖项。 这是它的运作方式&#xff1a; npm list -g --depth0-g: 指定列表应包括全局安装的软件包。--depth0: 限制树形结…

SpringBoot 2.2.10 无法执行Test单元测试

很早之前的项目今天clone现在&#xff0c;想执行一个业务订单的检查&#xff0c;该检查的代码放在test单元测试中&#xff0c;启动也是好好的&#xff0c;当点击对应的方法执行Test的时候就报错 tip&#xff1a;已添加spring-boot-test-starter 所以本身就引入了junit5的库 No…

多表查询综合归纳

目录 1. 多表关系 1.1 一对多&#xff08;多对一&#xff09; 1.2 多对多 1.3 一对一 2. 多表查询概述 2.1 熟悉表 2.2 笛卡尔积 2.3 消除笛卡尔积 2.4 多表查询分类 3. 内连接 3.1 隐式内连接 3.2 显式内连接 4. 外连接 4.1 左外连接 4.2 右外连接 5. 自连接 …

python爬虫(二)爬取国家博物馆的信息

import requests from bs4 import BeautifulSoup# 起始网址 url https://www.chnmuseum.cn/zx/xingnew/index_1.shtml # 用于存储所有数据 all_data [] page 1 global_index 1 # 定义全局序号变量并初始化为1 while True:html_url requests.get(url).textif requests.get…

基于NI Vision和MATLAB的图像颜色识别与透视变换

1. 任务概述 利用LabVIEW的NI Vision模块读取图片&#xff0c;对图像中具有特征颜色的部分进行识别&#xff0c;并对识别的颜色区域进行标记。接着&#xff0c;通过图像处理算法检测图像的四个顶点&#xff08;左上、左下、右上、右下&#xff09;&#xff0c;并识别每个顶点周…