Java并发编程之Condition await/signal原理剖析

news2024/12/23 18:14:36

Java并发编程之Condition await/signal原理剖析

文章目录

  • Java并发编程之Condition await/signal原理剖析
    • Condition与Lock的关系
    • Condition实现原理
    • await()实现分析
    • signal()实现分析
    • Condition接口与Object监听器的区别

Condition与Lock的关系

Condition本身也是⼀个接口,其功能和wait/notify类似,如下所示:

public interface Condition {
	void await() throws InterruptedException;
	boolean await(long time, TimeUnit unit) throws InterruptedException;
	long awaitNanos(long nanosTimeout) throws InterruptedException;
	void awaitUninterruptibly();
	boolean awaitUntil(Date deadline) throws InterruptedException;
	void signal();
	void signalAll();
}

wait()/notify()必须和synchronized⼀起使用, Condition也必须和Lock⼀起使用。因此,在Lock的接口中,有⼀个与Condition相关的接口:

public interface Lock {
	void lock();
	void lockInterruptibly() throws InterruptedException;
	// 所有的Condition都是从Lock中构造出来的
	Condition newCondition();
	boolean tryLock();
	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	void unlock();
}

Condition实现原理

可以发现, Condition的使用很方便,避免了wait/notify的生产者通知生产者、消费者通知消费者的问题。由于Condition必须和Lock⼀起使用,所以Condition的实现也是Lock的⼀部分。首先查看互斥锁和读写锁中Condition的构造方法:

public class ReentrantLock implements Lock, java.io.Serializable {
    // ...
    public Condition newCondition() {
        return sync.newCondition();
    }
}
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    // ...
    private final ReentrantReadWriteLock.ReadLock readerLock;
    private final ReentrantReadWriteLock.WriteLock writerLock;
    // ...
    public static class ReadLock implements Lock, java.io.Serializable {
        // 读锁不⽀持Condition
        public Condition newCondition() {
// 抛异常
            throw new UnsupportedOperationException();
        }
    }
    public static class WriteLock implements Lock, java.io.Serializable {
        // ...
        public Condition newCondition() {
            return sync.newCondition();
            }
			// ...
        }
		// ...
}

首先,读写锁中的 ReadLock 是不⽀持 Condition 的,读写锁的写锁和互斥锁都⽀持Condition。虽然它们各自调用的是自己的内部类Sync,但内部类Sync都继承自AQS。因此,上面的代码sync.newCondition最终都调用了AQS中的newCondition:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer
        implements java.io.Serializable {
    public class ConditionObject implements Condition, java.io.Serializable {
		// Condition的所有实现,都在ConditionObject类中
    }
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    final ConditionObject newCondition() {
        return new ConditionObject();
    }
}

有上面代码可知,调用 Lock.newConditon() 方法实际是调用的 Sync内部类中的方法创建了Condition实现了 ConditionObject()。ConditionObject 类是 同步器 AQS 的内部类,因为 Condition 的操作需要相关联的锁,每一个Condition对象上面,都阻塞了多个线程。因此,在 ConditionObject 内部也有一个双向链表组成的队列,如下所示:

在这里插入图片描述

public class ConditionObject implements Condition, java.io.Serializable {
    private transient Node firstWaiter;
    private transient Node lastWaiter;
}
static final class Node {
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
}

下面来看⼀下在await()/notify()方法中,是如何使用这个队列的。

await()实现分析

    public final void await() throws InterruptedException {
		// 刚要执⾏await()操作,收到中断信号,抛异常
        if (Thread.interrupted())
            throw new InterruptedException();
		// 加⼊Condition的等待队列
        Node node = addConditionWaiter();
		// 阻塞在Condition之前必须先释放锁,否则会死锁
        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);
    }

由上面的源码可知,如果一个线程调用了Condition.await()方法,那么该线程将会释放锁、构造成节点加入等待队列并进入等待状态。当调用await()方法时,相当于同步队列的首节点(获取了锁的节点)移动到Condition的等待队列中。

在这里插入图片描述

关于await,有几个关键点要说明:

  1. 线程调用await()的时候,肯定已经先拿到了锁。所以,在 addConditionWaiter()内部,对这个双向链表的操作不需要执行CAS操作,线程天生是安全的,代码如下:

        private Node addConditionWaiter() {
    		// ...
            Node t = lastWaiter;
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
    
  2. 在线程执行wait操作之前,必须先释放锁。也就是fullyRelease(node),否则会发⽣死锁。这个和wait/notify与synchronized的配合机制⼀样。

  3. 线程从wait中被唤醒后,必须用acquireQueued(node, savedState)方法重新拿锁。 线程从wait中被唤醒后,只是从等待队列转移到同步队列中,仍然需要在同步队列中排队争取锁。

  4. checkInterruptWhileWaiting(node)代码在park(this)代码之后,是为了检测在park期间是否收到过中断信号。当线程从park中醒来时,有两种可能:一种是其他线程调用了unpark,另⼀种是收到中断信号。这里的await()方法是可以响应中断的,所以当发现自己是被中断唤醒的,而不是被unpark唤醒的时,会直接退出while循环, await()方法也会返回。

  5. isOnSyncQueue(node)用于判断该Node是否在AQS的同步队列里面。初始的时候, Node只在Condition的队列里,而不在AQS的队列里,但执行notity操作的时候,会放进AQS的同步队列。

signal()实现分析

    public final void signal() {
		// 只有持有锁的线程,才有资格调⽤signal()⽅法
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
			// 发起通知
            doSignal(first);
    }

    // 唤醒队列中的第1个线程
    private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) && (first = firstWaiter) != null);
    }

	final boolean transferForSignal(Node node) {
        if (!node.compareAndSetWaitStatus(Node.CONDITION, 0))
        	return false;
		// 先把Node放⼊互斥锁的同步队列中,再调⽤unpark⽅法
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !p.compareAndSetWaitStatus(ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
        	return true;
    }

调用Condition的signal()方法,将会唤醒在等待队列中等待时间最长的节点(首节点),在唤醒节点之前,会将节点移到同步队列中。
在这里插入图片描述

同 await()⼀样,在调用 signal()的时候,必须先拿到锁(否则就会抛出上面的异常),是因为前面执行await()的时候,把锁释放了。

然后,从队列中取出firstWaiter,唤醒它。在通过调用unpark唤醒它之前,先用enq(node)方法把这个Node放入AQS的锁对应的阻塞队列中。 也正因为如此,才有了上面await()方法里面的判断条件:

while( !isOnSyncQueue(node))

这个判断条件满足,说明await线程不是被中断,而是被unpark唤醒的。

notifyAll()与此类似。

Condition接口与Object监听器的区别

Condition接口也提供了类似Object的监视器方法具体区别包括:

在这里插入图片描述

而且Condition的unpark方法可以指定线程唤醒,而Object的notify只能唤醒等待队列的任一个线程。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenStack的简单部署

OpenStack的简单部署 文章目录OpenStack的简单部署一、OpenStack概述二、环境准备三、搭建流程1. 更新 & 升级2. 安装好用的vim VimForCpp3. 安装必要依赖4. 关闭防火墙、核心防护、NetworkManager5. 配置静态IP地址6.配置yum源7. 安装时间同步服务8. 使用packstack 一键部…

C罗老矣,我的程序人生还有多远

☆ 随着12月11号摩洛哥1-0葡萄牙比赛的结束,不仅说明葡萄牙对要结束本届卡塔尔世界杯了,就连C罗此生的世界杯之旅也将画上句号了。 ☆ 37岁的球星本该是人生最璀璨的阶段,但在足球生涯中,这已经是大龄了。不禁让我想到&#xff0c…

机器视觉(五):机器视觉与世界杯

11月22日晚上,球迷再次为阿根廷而惋惜。在当天晚上进行的世界杯小组赛C组首轮比赛中,阿根廷队1:2不敌沙特阿拉伯队,爆出了本届世界杯开赛至今最大的冷门。 天台好冷不仅如此,阿根廷队全场比赛总计被吹罚了10次越位,刷新…

SpringMVC(一) 构建项目

SpringMVC(一) 构建项目 1.创建项目 创建一个空的Maven项目 删除src目录,将新建的项目作为一个工作空间使用,然后在里面创建Module。 2.创建Module 选中刚才创建的项目,右键创建Module 选择Java语言的Maven 项目 3.添加SpringMVC依赖 在…

1-48-mysql-基础篇-DML-select

1-mysql-基础篇: 推荐网站 mysql:https://dev.mysql.com/doc/refman/8.0/en/ 算法:https://www.cs.usfca.edu/~galles/visualization/about.html 数据库 1、数据库概述相关 1、 数据库的相关概念 DB:数据库(Data…

git 多用户配置(公司/个人)

背景 张三是一个程序员,他的英文名叫 outlaw,emial: outlaw163.com。 张三入职了一家公司,公司给张三的企业邮箱是 zhangsancompany.com 这一次,他 0 元购了一台新笔记本,需要配置一下 git git 账号配置 配置全局用…

微信公众号开发,获取openid,授权登录 WeChat-official-account-openid

微信公众号开发 功能:自动登录,获取个人信息,上传图片 超多麻烦的情况,怎样获取openid呢? 以下我给大家提供源码,文本,视频资料 保证让你看了就明白哈 look效果 1.拉起用户授权 2.后台获取到…

微服务雪崩问题解决 Sentinel

雪崩问题以及解决方案 限流设置 达到阈值的效果 隔离和降级 熔断 授权规则 设置规则持久化 雪崩问题以及解决方案雪崩微服务调用链路中的某个服务出现故障,引起链路上其他服务都不可用,这就是雪崩解决方案超时处理,设定超时时间,请…

【软件工程期末复习内容】

前言 时不可以苟遇,道不可以虚行。 一、软件工程的概念 软件是计算机系统运行的 指令、数据 和 相关文档 的集合,即软件等于程序、数据、加上文档。程序:是事先按照预定功能性能等要求设计和编写的指令序列;数据:是使…

Python编程 while循环

作者简介:一名在校计算机学生、每天分享Python的学习经验、和学习笔记。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.循环控制 1.循环控制介绍 2.while循环表达式 3.while循环表达式 4.b…

Python实现ALO蚁狮优化算法优化支持向量机回归模型(SVR算法)项目实战

说明:这是一个机器学习实战项目(附带数据代码文档视频讲解),如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 蚁狮优化(Ant Lion Optimizer,ALO)算法是Mirjalili于2015提出的一种新型元启发式群智能算法…

47-linux-vim-安装以及权限等-缺少

47-linux-vim-安装以及权限等: vi编辑器简介 vim是一个全屏幕纯文本编辑器,是vi编辑器的增强版,我们主要讲解的是vim编辑器。可以利用别名让输入vi命令的时候,实际上执行vim编辑器,例如: [rootlocalhost ~]# alias v…

HTTP协议介绍

了解HTTP HTTP是什么呢?它是超文本传输协议,HTTP是缩写,它的全英文名是HyperText Transfer Protocol。 那么什么是超文本呢? 超文本指的是HTML,css,JavaScript和图片等,HTTP的出现是为了接收和…

一位全栈工程师转岗项目经理的初体验与总结

从上周开始,公司这边把我从全栈工程师的岗位调到了项目经理的岗位,开始尝试管理岗位,感觉换了一个岗位像是换了一份工作一样,又在次充满了干劲。开始新的项目,招纳新的项目成员,虽然都是在做软件开发的事情…

【手把手】分布式定时任务调度解析之Quartz

1、任务调度背景 在业务系统中有很多这样的场景: 1、账单日或者还款日上午 10 点,给每个信用卡客户发送账单通知,还款通知。如何判断客户的账单日、还款日,完成通知的发送? 2、银行业务系统,夜间要完成跑批…

CCF CSP认证——201312

文章目录201312-1 出现次数最多的数201312-2 ISBN号码201312-3 最大的矩形201312-4 有趣的数201312-5 I’m stuck!201312-1 出现次数最多的数 题目链接 数据量较小,且数据范围也比较小。可以直接暴力,通过设置数组记录下标数据出现的次数,最…

C/C++关键字

C/C关键字【1】extern "C"【2】asm【3】关键字auto【4】break语句【5】catch 语句【6】关键字class【7】关键字const【8】#if【9】#pragma once【10】#pragma pack(1)【11】#pragma pack(4)【12】explicit【】 continue语句【13】关键字enum【14】friend【15】goto语…

【springboot进阶】基于starter项目构建(二)构建starter项目-web

目录 一、创建 web-spring-boot-starter 项目 二、添加 pom 文件依赖 三、构建配置 1. rest模板配置 RestTemplateConfig 2. 统一异常处理 BackendGlobalExceptionHandler 3. 统一返回数据结构 4. jwt鉴权处理 5. 请求日志切面处理 WebLogAspect 6. 邮件配置 BackendM…

mysql数据同步到elasticsearch数据解决方案

mysql数据同步到elasticsearch数据解决方案 问题场景 1.分库分表后多关联或者多条件查找效率低下,例如2b场景的查询,导出等需要多条件查询,继续用分库分表话效率低下。 2.数据量太多需要转移非关系型数据库elasticsearch存储 3.其他数据转…

AI 实战篇 |基于 AI开放平台实现 【植物识别】 功能,成为行走的百科全书

🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏制作专栏推荐:游戏制作 &…