并发编程之线程通信及Condition的原理分析

1. synchronized中的线程通信

调用wait方法会使线程处于等待状态,直到另一个线程调用notify线程才会唤醒等待中的某个线程,生产者和消费者模型可以很好的使用到该例子。

生产者代码:

public class Producer implements Runnable {private Queue<String> bags;private int maxSize;public Producer(Queue<String> bags, int maxSize) {this.bags = bags;this.maxSize = maxSize;}@Overridepublic void run() {int i = 0;while (true) {i++;synchronized (bags) {if (bags.size() == maxSize) {System.out.println("bags 满了");try {bags.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("生产者生产: bag" + i);bags.add("bag" + i);bags.notify();    //表示当前已经生产了数据,提示消费者可以消费了}}}
}

消费者代码:

public class Consumer implements Runnable{private Queue<String> bags;private int maxSize;public Consumer(Queue<String> bags, int maxSize) {this.bags = bags;this.maxSize = maxSize;}@Overridepublic void run() {int i = 0;while (true) {synchronized (bags) {if (bags.isEmpty()) {System.out.println("bags为空");try {bags.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}String bag = bags.remove();System.out.println("消费者消费:" + bag);bags.notify();    //这里只是唤醒Producer线程,并不能马上执行} // 同步代码块执行结束,monitorexit指令执行完成}}
}

客户端代码:

public class ProducerConsumerExample {public static void main(String[] args) throws InterruptedException {LinkedList<String> strings = new LinkedList<>();Producer producer = new Producer(strings, 10);Consumer consumer = new Consumer(strings, 10);new Thread(producer).start();Thread.sleep(100);new Thread(consumer).start();}
}

分析:客户端中分别启动了两个线程producer和consumer,producer中当生产的bag数达到最大值时,就会处于阻塞状态,此时消费者开始消费,消费者每消费一个,就会去唤醒处于阻塞中的producer线程,但是producer线程并不会立即执行,当它被唤醒后,此时consumer线程也在执行中,因此它们两个线程会去竞争锁,谁竞争到了就执行谁,直到consumer线程把bags消费完后,才会阻塞。

1.1 线程通信的猜想条件

1.wait和notify必须在synchronized中,有锁的竞争才会存在线程之间的通信

2.它们是基于共享的数据结构进行通信的,通信的话必然涉及到线程,而线程又是并行的,因此需要一个条件的变量去做互斥。

3.在等待或唤醒的过程中,需要进行一个等待或唤醒的通信,必须要有一个队列来进行处理

synchronized的底层线程通信:

2.Condition设计猜想‘

wait/notify,锁的实现是synchronized

在J.U.C中,锁的实现是Lock,而Condition的作用就类似于wait/notify

猜想:

1.作用:实现线程的阻塞和唤醒

2.前提条件:必须要先获得锁

3.await->让线程阻塞,并且释放锁

4.signal->唤醒阻塞的线程

5.加锁的操作,必然会涉及到AQS的阻塞队列

6.await释放锁的时候->AQS队列中是不会存在已经释放锁的线程的,这个被释放的线程去了哪里?

        await方法释放的线程,必须要有一个地方来存储,并且还需要被阻塞:->那么就会存在一个等待队列,LockSuppart.park阻塞

7.signal唤醒被阻塞的线程,从哪里唤醒?

        上面猜想到的等待队列中,唤醒一个线程,放哪里去?是不是应该再放到AQS队列中,去抢占锁。

2.1Condition源码分析之await

        public final void await() throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 1. 添加到等待队列Node node = addConditionWaiter();// 2. 完整的释放锁(考虑重入问题)int savedState = fullyRelease(node);int interruptMode = 0;while (!isOnSyncQueue(node)) {LockSupport.park(this);   // 3. 阻塞当前线程// 4. 要判断当前被阻塞的线程是否是因为interrupt唤醒的if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)break;}// 5. 重新竞争所,savedstate表示的是被释放锁的重入次数if (acquireQueued(node, savedState) && interruptMode != THROW_IE)interruptMode = REINTERRUPT;if (node.nextWaiter != null) // clean up if cancelledunlinkCancelledWaiters();if (interruptMode != 0)reportInterruptAfterWait(interruptMode);}

a.加入到Condition的等待队列中addConditionWaiter,并且node的节点状态被标记为CONDITION

        private Node addConditionWaiter() {Node t = lastWaiter;// If lastWaiter is cancelled, clean out.if (t != null && t.waitStatus != Node.CONDITION) {unlinkCancelledWaiters();t = lastWaiter;}Node node = new Node(Thread.currentThread(), Node.CONDITION);if (t == null)firstWaiter = node;elset.nextWaiter = node;lastWaiter = node;return node;}

b..完成的释放锁fullRelease,返回的savedState表示重入次数

    final int fullyRelease(Node node) {boolean failed = true;try {int savedState = getState();// 释放锁if (release(savedState)) {failed = false;return savedState;} else {throw new IllegalMonitorStateException();}} finally {if (failed)node.waitStatus = Node.CANCELLED;}}

c.判断node是否在同步队列中isOnSyncQueue(node)

    final boolean isOnSyncQueue(Node node) {// 状态仍是CONDITION那一定不在,同步队列会有一个冗余的前置节点,如果prev无前置节点,则一定不在if (node.waitStatus == Node.CONDITION || node.prev == null)return false;// 如果next节点不为空,则一定在if (node.next != null) // If has successor, it must be on queuereturn true;// 兜底方法,自旋遍历整个队列链表return findNodeFromTail(node);}

d..如果不在同步队列中,则就阻塞。 LockSupport.park(this); 

e..如果当前节点是在同步队列中,就会去竞争锁1.同步队列的prev 为head,则会去尝试竞争锁2.如果prev不为head,则会将状态由CONDITION使用CAS机制更改为SIGNAL

    final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();// 1. 去竞争锁if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}// 修改Node节点的状态if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

f..处理interrupt的中断响应checkInterruptWhileWaiting(node),LockSupport.park已经把当前线程挂起了,因此到这个地方一个可能是interrupt中断从而唤醒了该线程,而另一个可能就是Signal唤醒了该线程

    private int checkInterruptWhileWaiting(Node node) {return Thread.interrupted() ?(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :0;}

分析:如果不是interrupt唤醒的,则无事发生,返回0;如果是intertupt唤醒的,则执行transferAfterCancelledWait(node),判断线程是发出SIgNAL之前还是之后,如果在发出信号之前,执行enq(node),加入到同步队列,抛出中断异常,如果是在发出SIGNAL信号之后,则可能出现的情况时,线程A发出了SIGNAL信号,但是还没有将线程同步到同步队列呢,因此执行Thread,yield先让线程缓一缓,保证同步到同步队列中,之后再正常中断掉该线程

    final boolean transferAfterCancelledWait(Node node) {// 发生在signal信号之前  会抛出异常if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {enq(node);return true;}// 上面CAS失败,说明在Signal之后了。如果还不在同步队列中,则让线程先让步,缓一缓while (!isOnSyncQueue(node))Thread.yield();return false;}

2.2Condition源码分析之signal

        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 (!compareAndSetWaitStatus(node, Node.CONDITION, 0))return false;// 1. 将节点加入到同步队列中Node p = enq(node);int ws = p.waitStatus;// 2.修改节点状态为SIGNAL  并且唤醒这个node  是等待队列中的node的线程if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))LockSupport.unpark(node.thread);return true;}

分析: signal主要做了两件事,拿到等待队列中的Node,将它加入到同步队列中,并更新状态为SIGNAL,然后唤醒刚才等待队列中的那个node,再回到wait中的LockSupport.park方法之后接着执行2.1中的e和f。

3.总结

1.线程的通信是基于在同一个锁下,且线程之间存在竞争,并且还需要有一个共享的资源,彼此之间基于共享资源来进行交互的一种通信方式。

2.wait/notify是基于synchronized同步锁实现的

3.Condition是J.U.C中的实现,基于Lock锁

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

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

相关文章

虚拟现实(VR)的应用场景

虚拟现实&#xff08;VR&#xff09;技术创建和体验三维虚拟世界的计算机仿真技术。用户通过佩戴VR头显等设备&#xff0c;可以完全沉浸在虚拟世界中&#xff0c;并与虚拟世界中的物体进行交互。VR技术具有广泛的应用前景&#xff0c;可以应用于各行各业。以下是一些VR的应用场…

STM32标准库编程与51单片机直接写寄存器的区别和联系

简介&#xff1a; 在学完51单片机之后&#xff0c;我们去学习32的时候&#xff0c;会发现编程的方法有很大的区别&#xff0c;让人非常的不适应&#xff0c;但是通过不断的调用相应外设的库函数之后&#xff0c;你也可以去编程STM32&#xff0c;来实现功能&#xff0c;但是你真…

SQL的基础语句

1、select语句 select colums from table_name 2、条件语句 #查询出查询出用户id为1和3的用户记录 IN 操作符允许我们在 WHERE 子句中规定多个值。 select * from student where id in (1,3) #查询出所有姓王的同学 模糊查询 like 通配符(% 任意多个字符 _单个字符) #下例…

如何使用渐变块创建自定义聊天机器人

如何使用渐变块创建自定义聊天机器人 文章目录 如何使用渐变块创建自定义聊天机器人一、介绍二、参考示例1、一个简单的聊天机器人演示2、将流式传输添加到您的聊天机器人3、喜欢/不喜欢聊天消息4、添加 Markdown、图像、音频或视频 一、介绍 **重要提示&#xff1a;**如果您刚…

软考高级架构师:AI 通俗讲解负载测试、压力测试、强度测试、容量测试和可靠性测试

在软件工程领域&#xff0c;测试是一个确保软件质量和性能的关键步骤。负载测试、压力测试、强度测试、容量测试和可靠性测试都是性能测试的不同类型&#xff0c;它们的目的和方法有所不同。 下面我将通过简单的比喻和解释&#xff0c;帮助您理解这些测试之间的区别。 负载测试…

跳跃游戏 II (贪心, 动态规划)

题目描述(力扣45题) : 给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说&#xff0c;如果你在 nums[i] 处&#xff0c;你可以跳转到任意 nums[i j] 处: 0 < j < nums[i] i j < n 返回到…

使用Unity扫描场景内的二维码,使用插件ZXing

使用Unity扫描场景内的二维码&#xff0c;使用插件ZXing 使用Unity扫描场景内的二维码&#xff0c;ZXing可能没有提供场景内扫描的方法&#xff0c;只有调用真实摄像机扫描二维码的方法。 实现的原理是&#xff1a;在摄像机上添加脚本&#xff0c;发射射线&#xff0c;当射线打…

【面试八股总结】Linux系统下的I/O多路复用

参考资料 &#xff1a;小林Coding、阿秀、代码随想录 I/O多路复用是⼀种在单个线程或进程中处理多个输入和输出操作的机制。它允许单个进程同时监视多个文件描述符(通常是套接字)&#xff0c;一旦某个描述符就绪&#xff08;一般是读就绪或者写就绪&#xff09;&#xff0c;能够…

分享三个转换速度快、准确率高的视频转文字工具

想要直接将视频转换成文字&#xff0c;转换工具很重要&#xff01;给大家分享三个转换速度快、准确率高的视频转文字工具&#xff0c;轻松完成转换。 1.网易见外 https://sight.youdao.com/ 网易家的智能转写翻译服务工作站&#xff0c;网页端就可以直接使用&#xff0c;支持视…

Spring Bean依赖注入-Spring入门(二)

1、SpringBean概述 在Spring中&#xff0c;一切Java对象都被视为Bean&#xff0c;用于实现某个具体功能。 Bean的依赖关系注入的过程&#xff0c;也称为Bean的装配过程。 Bean的装配方式有3种&#xff1a; XML配置文件注解Java类 Spring中常用的两种装配方式分别是基于XML的…

Codeforces Round 821 (Div. 2) D2. Zero-One

题目 #include <bits/stdc.h> using namespace std; #define int long long #define pb push_back #define fi first #define se second #define lson p << 1 #define rson p << 1 | 1 const int maxn 1e5 5, inf 1e18, maxm 4e4 5; const int N 1e6;c…

【MySQL】InnoDB与MyISAM存储引擎的区别与选择

存储引擎就是存储数据、建立索引、更新/查询数据等技术的实现方式 。 存储引擎是基于表的&#xff0c;而不是基于库的&#xff0c;所以存储引擎也可被称为表类型。我们可以在创建表的时候&#xff0c;来指定选择的存储引擎&#xff0c;如果没有指定将自动选择默认的存储引擎。…

【工具-PyCharm】

工具-PyCharm ■ PyCharm-简介■ PyCharm-安装■ PyCharm-使用■ 修改主题■ 设置字体■ 代码模板■ 解释器配置■ 文件默认编码■ 快捷键■ 折叠■ 移动■ 注释■ 编辑■ 删除■ 查看■ 缩进■ 替换 ■ PyCharm-简介 官方下载地址 Professional&#xff1a;专业版&#xff0…

python--使用pika库操作rabbitmq实现需求

Author: wencoo Blog&#xff1a;https://wencoo.blog.csdn.net/ Date: 22/04/2024 Email: jianwen056aliyun.com Wechat&#xff1a;wencoo824 QQ&#xff1a;1419440391 Details:文章目录 目录正文 或 背景pika链接mqpika指定消费数量pika自动消费实现pika获取队列任务数量pi…

JavaScript(二)

JavaScript的语法 1.JavaScript的大小写 在JavaScript中&#xff0c;大小写是敏感的&#xff0c;这意味着大小写不同的标识符被视为不同的变量或函数。例如&#xff0c;myVariable 和 myvariable 被视为两个不同的变量。因此&#xff0c;在编写JavaScript代码时&#xff0c;必…

如何在PostgreSQL中创建并使用窗口函数来进行复杂的分析查询?

文章目录 解决方案1. 了解窗口函数的基本概念2. 常用的窗口函数3. 使用示例示例 1&#xff1a;计算每行销售数据的累计销售额示例 2&#xff1a;计算每行销售数据相对于前一行销售额的增长率 结论 PostgreSQL 提供了一套强大的窗口函数&#xff08;Window Functions&#xff09…

MQTT Broker 白皮书:全面实用的 MQTT Broker 选型指南

在智能数字化时代&#xff0c;家居设备、工厂传感器、智能汽车、能源电力计量表等各类设备都已变身为新型的智能终端。为了满足这些海量且持续增长的智能设备之间对于实时、可靠的消息传递的需求&#xff0c;MQTT Broker 消息代理或消息中间件扮演了至关重要的角色。作为新一代…

STM32H750外设ADC之模拟窗口看门狗

目录 概述 1 相关寄存器 2 功能描述 3 AWDx 标志和中断 4 模拟看门狗 1 4.1 模拟看门狗 1 说明 4.2 模拟看门狗通道选择 4.3 阀值选择 5 模拟看门狗 2和3 6 ADCx_AWDy_OUT 信号输出生成 6.1 功能介绍 6.2 输出信号案例 7 模拟看门狗 1、 2、 3 比较 概述 本文主…

Opencv_3_图像对象的创建与赋值

ColorInvert.h 如下&#xff1a; #include <opencv.hpp> using namespace std; #include <opencv.hpp> using namespace cv; using namespace std; class ColorInvert{ public : void mat_creation(); }; ColorInvert.cpp 文件如下&#xff1a; #include &q…

解决宝塔面板无法访问(无法访问或拒绝链接)

&#x1f40c;博主主页&#xff1a;&#x1f40c;​倔强的大蜗牛&#x1f40c;​ &#x1f4da;专栏分类&#xff1a;Linux ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 问题如下&#xff1a; 本人设置了授权IP&#xff0c;但是有些问题&#xff0c;所以是打算取消授权IP 重…