JUC 线程间通信

前言

本篇文章我将解释《并发编程的艺术》一书中一个经典的实现线程间通信的案例,主要是使用wait() 和 notifyAll() 方法来实现的。

这段代码的作用是通过 wait()notifyAll() 方法实现线程间的等待和通知机制。具体来说,代码中创建了两个线程,一个用于等待条件的满足,另一个用于在条件满足时通知等待的线程。通过对共享资源的加锁和释放,实现了线程间的协调和同步,确保线程按照期望的顺序执行。

样例展示

public class WaitNotify {
   static boolean flag = true;
   static Object lock = new Object();
   public static void main(String[] args) throws Exception {
      Thread waitThread = new Thread(new Wait(), "WaitThread");
      waitThread.start();
      TimeUnit.SECONDS.sleep(1);
      Thread notifyThread = new Thread(new Notify(), "NotifyThread");
      notifyThread.start();
   }
   static class Wait implements Runnable {
      public void run() {
         // 加锁,拥有lock的Monitor
         synchronized (lock) {
            // 当条件不满足时,继续wait,同时释放了lock的锁
            while (flag) {
               try {
                   System.out.println(Thread.currentThread() + " flag is true. wait@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                   lock.wait();
               } catch (InterruptedException e) {
               }
            }
            // 条件满足时,完成工作
            System.out.println(Thread.currentThread() + " flag is false. running@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
         }
      }
   }
   static class Notify implements Runnable {
      public void run() {
         // 加锁,拥有lock的Monitor
         synchronized (lock) {
            // 获取lock的锁
            // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
            System.out.println(Thread.currentThread() + " hold lock. notify @ " +new SimpleDateFormat("HH:mm:ss").format(new Date()));
            lock.notifyAll();
            flag = false;
            SleepUtils.second(5);
         }
          // 再次加锁
          synchronized (lock) {
             System.out.println(Thread.currentThread() + " hold lock again. sleep@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
             SleepUtils.second(5);
          }
      }
   }
}

代码解析

首先主方法中定义了一个falg变量和一个object对象lock。

static boolean flag = true;

随后创建了一个Wait线程,并使其进入就绪态,然后沉睡一秒,随后创建Notify线程让其进入就绪态,因为线程沉睡了一秒,所以一定是Wait线程先从就绪态进入执行态。

 Thread waitThread = new Thread(new Wait(), "WaitThread");
 waitThread.start();
 TimeUnit.SECONDS.sleep(1);
 Thread notifyThread = new Thread(new Notify(), "NotifyThread");
 notifyThread.start();

所以下面来看一下Wait类中具体代码,Wait线程开始运行。

首先它是一个继承了Runnable接口的线程类,该线程在运行时首先对lock对象进行上锁时其他线程不能对其上锁,如果当前lock已被上锁则这里不会进入执行。

synchronized (lock)

如果进入执行后会进入循环,循环内先进行一个输出,随后让其因为lock进入等待状态并释放锁。

            while (flag) {
               try {
                   System.out.println(Thread.currentThread() + " flag is true. wait@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
                   lock.wait();
               } catch (InterruptedException e) {
               }
            }

之后轮到Notify线程进入运行态,因为之前锁已经被释放,因此此时可以对lock重新上锁并进入执行。执行时先进行了一个输出,然后进行唤醒,lock.notifyAll();表示唤醒所有因为lock而进入等待的线程,这里就是把Wait线程唤醒.

synchronized (lock) {
   // 获取lock的锁
   // 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
   System.out.println(Thread.currentThread() + " hold lock. notify @ " +new 
   SimpleDateFormat("HH:mm:ss").format(new Date()));
   lock.notifyAll();
   flag = false;
   SleepUtils.second(5);
}

再将flag变量设为false,沉睡5秒。这里要注意,该线程虽然程序了五秒,但该部分代码并未完全执行完成,因此在这5秒内并不会释放锁,5秒后该部分代码才是真正执行完毕并解锁。

之后的情况看时间片执行情况,如果此时时间片还未耗尽,则会接着执行该类下面的代码,对该线程重新上锁并执行,执行为一个输出并沉睡5秒。随后释放锁。

synchronized (lock) {
   System.out.println(Thread.currentThread() + " hold lock again. sleep@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
   SleepUtils.second(5);
}

但是如果在沉睡5秒后时间片刚好耗尽,那么下次执行时就可能会是Wait线程重新获得锁,并继续执行,这里有一点需要注意:

一个线程在进入等待状态后再重新进入运行态后会接着上次运行的位置继续执行,因此此时运行时会接着运行lock.wait();后面的代码,

System.out.println(Thread.currentThread() + " flag is false. running@ " + new SimpleDateFormat("HH:mm:ss").format(new Date()));

后面该线程进行了一段输出后代码执行完毕再次释放锁。至此整体代码执行完毕。

最后执行结果如下:

因此总体而言,这段代码展示了使用 wait()notifyAll() 方法进行线程间通信的基本模式,其中一个线程等待某个条件的满足,而另一个线程负责在条件满足时通知等待的线程。

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

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

相关文章

论文阅读-Multiple Targets Directed Greybox Fuzzing (Hongliang Liang,2024)

标题: Multiple Targets Directed Greybox Fuzzing (Hongliang Liang,2024) 作者: Hongliang Liang, Xinglin Yu, Xianglin Cheng, Jie Liu, Jin Li 期刊: IEEE Transactions on Dependable and Secure Computing 研究问题: 发现局限性:之前的定向灰盒测试在有…

webAssembly学习及使用rust

学习理解 webAssembly 概念知识,使用 API 进行 web 前端开发。 概念 是一种运行在现代网络浏览器中的新型代码,并且提供新的性能特性和效果。它有一种紧凑的二进制格式,使其能够以接近原生性能的速度运行。C/C、 C#、Rust等语言可以编译为 …

ruby 配置代理 ip(核心逻辑)

在 Ruby 中配置代理 IP,可以通过设置 Net::HTTP 类的 Proxy 属性来实现。以下是一个示例: require net/http// 获取代理Ip:https://www.kuaidaili.com/?refrg3jlsko0ymg proxy_address 代理IP:端口 uri URI(http://www.example.com)Net:…

【React】Sigma.js框架网络图-入门篇

一、介绍 Sigma.js是一个专门用于图形绘制的JavaScript库。 它使在Web页面上发布网络变得容易,并允许开发人员将网络探索集成到丰富的Web应用程序中。 Sigma.js提供了许多内置功能,例如Canvas和WebGL渲染器或鼠标和触摸支持,以使用户在网页上…

MATLAB R2024a:重塑商业数学软件的未来

在数字化浪潮席卷全球的今天,商业数学软件已经成为企业、研究机构乃至个人不可或缺的工具。而在这其中,MATLAB R2024a以其卓越的性能和广泛的应用领域,正逐步成为商业数学软件的新标杆。 MATLAB R2024a不仅继承了前代版本的优秀基因&#xf…

Golang 采集爬虫如何配置代理 IP

在 Golang 中配置代理 IP,可以通过设置 http.Transport 的 Proxy 属性来实现: 下述代码中的 代理IP 和 端口 替换为实际的代理服务器地址和端口,然后运行该程序即可通过代理服务器访问对应网站。 package mainimport ("fmt""…

超详细的Maven安装与使用还有内容讲解

文章目录 作用简介模型仓库 安装配置IDEA配置Maven坐标概念主要组成 IDEA创建Maven项目基本使用常用命令生命周期使用坐标导入jar包 注意事项清理maven仓库更新索引依赖 作用 Maven是专门用于管理和构建Java项目的工具,它的主要功能有: 提供了一套标准化…

MATLAB实现禁忌搜索算法优化柔性车间调度fjsp

禁忌搜索算法的流程可以归纳为以下几个步骤: 初始化: 利用贪婪算法或其他局部搜索算法生成一个初始解。清空禁忌表。设置禁忌长度(即禁忌表中禁止操作的期限)。邻域搜索产生候选解: 通过特定的搜索算子(如…

AWS账号注册以及Claude 3 模型使用教程!

哈喽哈喽大家好呀,伙伴们!你听说了吗?最近AWS托管了大热模型:Claude 3 Opus!想要一探究竟吗?那就赶紧来注册AWS账号吧!别担心,现在注册还免费呢!而且在AWS上还有更多的大…

【北京迅为】《iTOP-3588开发板系统编程手册》-第10章 存储映射 I/O

RK3588是一款低功耗、高性能的处理器,适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用,RK3588支持8K视频编解码,内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像素ISP&…

Spark-Scala语言实战(17)

我带着大家一起来到Linux集群环境下,学习我们的spark。想了解的朋友可以查看这篇文章。同时,希望我的文章能帮助到你,如果觉得我的文章写的不错,请留下你宝贵的点赞,谢谢。 Spark-Scala语言实战(16&#x…

基于Springboot的社区帮扶对象管理系统(有报告)。Javaee项目,springboot项目。

演示视频: 基于Springboot的社区帮扶对象管理系统(有报告)。Javaee项目,springboot项目。 项目介绍: 采用M(model)V(view)C(controller)三层体系…

微信小程序日期增加时间完成订单失效倒计时(有效果图)

效果图 .wxml <view class"TimeSeond">{{second}}</view>.js Page({data: {tiem_one:,second:,//倒计时deadline:,},onLoad(){this.countdown();},countdown(){let timestamp Date.parse(new Date()) / 1000;//当前时间戳let time this.addtime(2024…

数据结构- 顺序表-单链表-双链表 --【求个关注!】

文章目录 一 顺序表代码&#xff1a; 二 链表单链表双向链表 一 顺序表 顺序表是线性表的一种 所谓线性表指一串数据的组织存储在逻辑上是线性的&#xff0c;而在物理上不一定是线性的 顺序表的底层实现是数组&#xff0c;其由一群数据类型相同的元素组成&#xff0c;其在逻辑…

JVM知识点总结二

参考文章&#xff1a;【Java面试题汇总】JVM篇&#xff08;2023版&#xff09;_jvm面试题2023-CSDN博客 1、说说你了解的JVM内存模型&#xff1a; JVM由三部分组成&#xff1a;类加载子系统、运行时数据区、执行引擎 JVM内存模型&#xff1a; 内存模型里的运行时数据区&#…

STM32实现硬件I2C通讯,读取MPU6050的ID号

今天学习了使用硬件I2C的方式成功读取MPU6050的ID号&#xff0c;特此记录一下过程&#xff1a; 首先需要学习的是MPU6050的初始化&#xff1a; 第一步&#xff1a;打开GPIOB的时钟&#xff08;因为I2C2的引脚10,11在GPIOB上&#xff09; 第二步&#xff1a;打开I2C2的时钟 …

LLAMA 3的测试之旅:在GPT-4的阴影下前行

Meta终于发布了他们长期期待的LLAMA 3模型&#xff0c;这是一个开源模型&#xff0c;实际上提供了一系列新的功能&#xff0c;使得模型在回答问题时表现得更好。这对AI社区来说是一个真正的里程碑事件。 Meta正在发布新版本的Meta AI&#xff0c;这是一种可以在他们的应用程序和…

用Python在PDF文档中插入单图像水印和平铺图像水印

PDF文档因其跨平台兼容性和内容保真度成为信息交换的标准载体&#xff0c;为应对版权侵犯、内容篡改以及未经授权的传播等风险&#xff0c;向PDF中插入图片水印成为一种强化文档安全性、彰显所有权及实施访问控制的有效手段。图片水印不仅能以直观的方式标示文档来源、强化版权…

Git学习笔记(三)Git分支

Git分支是Git中非常重要的一个概念&#xff0c;无论是个人开发还是多人协作中&#xff0c;分支都起着至关重要的作用。几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离 开来进行重大的Bug修改、开发新的功能&#xff0c;以免影响…

Discuz! X3.4 升级至 Discuz! X3.5 详细教程

第一步&#xff1a;从其他以前的 Discuz! X 版本升级Discuz! X3.4 请先升级到Discuz! X3.4&#xff0c;升级教程网上比较普遍&#xff0c;在此不再论述。 第二步&#xff1a;Discuz! X3.4 升级至 Discuz! X3.5 &#xff08;Discuz 从 X3.5 以后&#xff0c;不在发布GBK版本&…
最新文章