多线程和并发(1)—等待/通知模型
一、进程通信和进程同步
1.进程通信的方法
同一台计算机的进程通信称为IPC(Inter-process communication),不同计 算机之间的进程通信被称为 RPC(Romote process communication),需要通过网络,并遵守共同的协议。**进程通信解决的问题是两个或多个进程间如何交换数据的问题。**常用的进程通信的方法如下:
- 管道:分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用 于具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外, 它还允许无亲缘关系进程间的通信。
- 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较 复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器 收到一个中断请求效果上可以说是一致的。
- 消息队列(message queue):消息队列是消息的链接表,它克服了上两 种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息 队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。 4. 共享内存(shared memory):可以说这是最有用的进程间通信方式。它 使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共 享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
- 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。
- 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络 中不同机器之间的进程间通信,应用非常广泛。同一机器中的进程还可以使用 Unix domain socket(比如同一机器中 MySQL 中的控制台 mysql shell 和 MySQL 服 务程序的连接),这种方式不需要经过网络协议栈,不需要打包拆包、计算校验 和、维护序号和应答等,比纯粹基于网络的进程间通信肯定效率更高。
2.线程同步的方法
**线程同步解决的问题是多个线程在并发执行过程中需要保持数据一致性和顺序性等问题。**常见的线程同步方法如下:
- 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
- 互斥量:为协调共同对一个共享资源的单独访问而设计的。互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。
- 信号量:为控制一个具有有限数量用户资源而设计。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。
- 事件: 用来通知线程有一些事件已发生,从而启动后继任务的开始。
二、创建线程的方法
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:
- 继承Thread类创建线程
- 实现Runnable接口创建线程
- 使用Callable和Future创建线程
- 使用线程池例如用Executor框架
重点说明(3)使用Callable和Future创建线程。和Runnable接口不一样,Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大,其实现了(1)call()方法可以有返回值;(2)call()方法可以声明抛出异常;
Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

FutureTask的常见方法如下:
- boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务
- V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
- V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException
- boolean isDone():若Callable任务完成,返回True
- boolean isCancelled():如果在Callable任务正常完成前被取消,返回True
介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:
- 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
- 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
- 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
- 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
代码实现:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;public class MyCallableTest {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask futureTask = new FutureTask<>(new MyCallableThread());Thread thread = new Thread(futureTask);thread.start();String result = futureTask.get();System.out.println(result);}
}class MyCallableThread implements Callable {@Overridepublic String call() throws Exception {System.out.println("thread running");Thread.sleep(3000);return "thread returned";}
}
三、线程中run()方法和执行线程start()的区别
Thread类是Java里对线程概念的抽象,可以这样理解:我们通过new Thread()其实只是new出一个Thread的实例,还没有操作系统中真正的线程关联起来。只有执行了start()方法后,才实现了真正意义上的启动线程。
从Thread的源码可以看到,Thread的start方法中调用了start0()方法,而start0()是个native方法,这就说明Thread#start一定和操作系统是密切相关的。
Thread类中的run()方法中说明的是任务的处理逻辑,执行线程的start()方法让一个线程进入就绪队列等待分配cpu,分到cpu后才调用实现的run()方法,执行任务的处理逻辑,start()方法不能重复调用,如果重复调用会抛出异常。而run方法是业务逻辑实现的地方,本质上和任意一个类的任意一个成员方法并没有任何区别,可以重复执行,也可以被单独调用。
四、线程中断的方法
1.自然终止
要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。
2.调用stop等方法
暂停、恢复和停止操作对应在线程Thread的API就是suspend()、resume()和stop()。但是这些API是过期的,也就是不建议使用的。不建议使用的原因主要有:以suspend()方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题。同样,stop()方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下。正因为suspend()、resume()和stop()方法带来的副作用,这些方法才被标注为不建议使用的过期方法。
public class MyThreadTest1 {public static void main(String[] args) {Thread thread = new Thread(new MyTask());thread.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}thread.stop();}
}class MyTask implements Runnable{@Overridepublic void run() {while (true) {System.out.println("thread runing: " + Thread.currentThread().getName());try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
3.使用thread.interrupt()中断方法
安全的停止线程方式是使用thread.interrupt()中断来停止。在主线程中调用thread.interrupt()方法,能够将thread的中断标识设置为false,再在当前线程中调用Thread.currentThread().isInterrupted()判断是否中断,从而判断是否结束线程。
值得注意的是如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、thread.wait等),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false,所以需要Thread.currentThread().interrupt()重新再设置一下。
代码实现:
public class MyTest1 {public static void main(String[] args) {Thread thread = new Thread(new MyTaskTest());thread.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt();System.out.println("mainThread end");}
}class MyTaskTest implements Runnable{@Overridepublic void run() {boolean interrupted = Thread.currentThread().isInterrupted();while (!interrupted) {interrupted = Thread.currentThread().isInterrupted();System.out.println("interrupted = " + interrupted);System.out.println("subThread running");try {Thread.sleep(2000);} catch (InterruptedException e) {
// e.printStackTrace();Thread.currentThread().interrupt();System.out.println("interrupted = " + interrupted);}}}
}
五、多线程中的等待/通知模式
thread.join()
join()是进行线程同步的方法,通过在当前线程中执行另一个线程的thread.join()方法,可以等待另一个线程执行完成之后,当前线程才执行,通过这样的方式来控制两个线程的先后顺序。
以下代码通过join()方法实现了thread1–>thread2–>thread3按照顺序来执行的逻辑。
public class MyJoinTest {public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(new MyTask1());Thread thread2 = new Thread(new MyTask2(thread1));Thread thread3 = new Thread(new MyTask3(thread2));thread1.start();thread2.start();thread3.start();thread3.join();System.out.println("main end");}
}class MyTask1 implements Runnable {@Overridepublic void run() {System.out.println("thread:" + Thread.currentThread().getName());try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}
}class MyTask2 implements Runnable {Thread thread;public MyTask2(Thread thread) {this.thread = thread;}@Overridepublic void run() {try {thread.join();System.out.println("thread:" + Thread.currentThread().getName());Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}
}class MyTask3 implements Runnable {Thread thread;public MyTask3(Thread thread) {this.thread = thread;}@Overridepublic void run() {try {thread.join();System.out.println("thread:" + Thread.currentThread().getName());Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}}
}
wait()/notiyf()
通过wait()/notiyf()来实现等待/通知模型,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
wait()方法:调用该方法的线程进入 WAITING状态,只有等待另外线程的通知或被中断才会返回.需要注意,调用wait()方法后,会释放对象的锁
notify()方法:通知一个在对象上等待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁,没有获得锁的线程重新进入WAITING状态。
等待/通知模型说明:
等待方:
(1) 获取对象的锁。
(2) 如果条件不满足,那么调用对象的wait()方法,此时会释放锁。
(3) 竞争到锁并且条件满足,则执行对应的逻辑。
synchronized(lock){while(条件不满足){lock.wait()}业务逻辑
}通知方:
(1) 获得对象的锁。
(2) 改变条件。
(3) 通知所有等待在对象上的线程,并释放锁。
synchronized(lock){改变条件,满足条件lock.notify()
}
以下代码实现:等待方等待通知方改变条件,满足条件后才执行后面的业务逻辑
public class MyWaitNotifyTest {public static Object lock = new Object();public static boolean flag = false;public static void main(String[] args) {Thread thread1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("等待方:我想要执行");while (!flag) {try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("等待方:正常执行了");}}});Thread thread2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {flag = true;lock.notify();System.out.println("通知方:可以执行了");}}});thread1.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}thread2.start();}
}
本文由博客一文多发平台 OpenWrite 发布!
相关文章:
多线程和并发(1)—等待/通知模型
一、进程通信和进程同步 1.进程通信的方法 同一台计算机的进程通信称为IPC(Inter-process communication),不同计 算机之间的进程通信被称为 RPC(Romote process communication),需要通过网络,并遵守共同的协议。**进…...
浏览器的事件循环
其实在我们电脑的操作系统中,每一个运行的程序都会由自己的进程(可能是一个,也可能有多个),浏览器就是一个程序,它的运行在操作系统中,拥有一组自己的进程(主进程,渲染进…...
跳跃游戏 II【贪心算法】
跳跃游戏 II class Solution {public int jump(int[] nums) {int cur 0;//当前最大覆盖路径int next 0;//下一步的最大覆盖路径int res 0;//存放结果,到达终点时最少的跳跃步数for (int i 0; i < nums.length; i) {//遍历数组,以给出数组以一个…...
promise
promise 属于事件循环的微任务,具体详见:事件循环 Promise 语法: const p1 new Promise((reslove,reject)>{console.log(2);reslove(1) }).then((data)>{console.log(3);console.log(data) }).catch((data)>{console.log(3); }) promise.th…...
前端面试:【网络协议与性能优化】HTTP/HTTPS、TCP/IP和WebSocket
嗨,亲爱的Web开发者!在构建现代Web应用时,了解网络协议是优化性能和确保安全性的关键。本文将深入探讨HTTP/HTTPS、TCP/IP和WebSocket这三个网络协议,帮助你理解它们的作用以及如何优化Web应用的性能。 1. HTTP/HTTPS协议…...
设计模式之工厂模式(万字长文)
文章目录 概述工厂模式的优点包括工厂模式有几种主要的变体看一个具体需求使用传统的方式来完成传统的方式的优缺点 简单工厂模式基本介绍使用简单工厂模式简单工厂模式的优缺点优点:缺点: 工厂方法模式看一个新的需求思路 1思路 2工厂方法模式介绍工厂方…...
CNN 02(CNN原理)
一、卷积神经网络(CNN)原理 1.1 卷积神经网络的组成 定义 卷积神经网络由一个或多个卷积层、池化层以及全连接层等组成。与其他深度学习结构相比,卷积神经网络在图像等方面能够给出更好的结果。这一模型也可以使用反向传播算法进行训练。相比较其他浅层或深度神经…...
Android View动画整理
View 动画相关内容可参考官网 动画资源 此前也有写 View 动画相关的内容,但都只是记录代码,没有特别分析。以此篇作为汇总、整理、分析。 Android View 动画有4中,分别是 平移动画 TranslateAnimation缩放动画 ScaleAnimation旋转动画 Rot…...
阿里云架构
负载均衡slb 分类以及应用场景 负载均衡slb clb 传统的负载均衡(原slb) 支持4层和7层(仅支持对uri(location),域名进行转发) 一般使用slb(clb) alb 应用负载均衡 只支持7层,整合了nginx负载均衡的各种功能,可以根据用户请求头,响应头 如果需要详细处理用户请求(浏…...
【C语言】操作符大全(保姆级介绍)
🚩纸上得来终觉浅, 绝知此事要躬行。 🌟主页:June-Frost 🚀专栏:C语言 🔥该篇将详细介绍各种操作符的功能。 目录: 📘 前言① 算术操作符②移位操作符③位操作符④赋值操…...
ruoyi-cloud部署
默认你已经安装mysql,nacos,seata,sentinel等(没有的可以先找教程安装) 1、下载源码:git clone https://gitee.com/zhangmrit/ruoyi-cloud 2、项目依赖导入,选择自己的maven环境等,创…...
Vue3(开发h5适配)
在开发移动端的时候需要适配各种机型,有大的,有小的,我们需要一套代码,在不同的分辨率适应各种机型。 因此我们需要设置meta标签 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…...
图的存储:邻接矩阵法
1.邻接矩阵的实现 邻接矩阵的定义:在无向图和有向图中,使用二维数组表示各个顶点的相邻情况:1代表相邻,0表示不相邻。 代码实现: #define MaxVertexNum 100//顶点数目的最大值 typedef struct {char Vex [MaxVertexN…...
如何优雅的使用Git?
第一部分:Git的基本概念和初始设置 Git是一个分布式版本控制系统,它允许多人共同工作,同时跟踪和管理项目的版本历史。使用Git,您可以恢复旧版本、创建新分支进行实验,并与其他开发者进行协作,而不会影响主…...
【【STM32分析IO该设置什么模式的问题】】
STM32分析IO该设置什么模式的问题 我们分析而言 我们对于PA0 的设计就从此而来 对于边沿触发的选择我们已经有所了解了 我们下拉,但是当我们摁下开关的时候 从0到1 导通了 所以这个是下拉 上升沿触发 而对于KEY0 我们摁下是使得电路从原来悬空高阻态到地就是0 所以…...
飞天使-k8s基础组件分析-服务与ingress
文章目录 服务的介绍服务代理服务发现连接集群外服务服务发布无头服务 服务,pod和dns的关系端口转发通过expose 暴露应用服务案例INGRESSMetalLB使用参考文档 服务的介绍 服务的作用是啥? 提供外部调用,保证podip的真实性看看服务解决了什么…...
Unity——拖尾特效
拖尾是一种很酷的特效。拖尾的原理来自人类的视觉残留:观察快速移动的明亮物体,会看到物体移动的轨迹。摄像机通过调整快门时间,也可以拍出具有拖尾效果的照片,如在城市的夜景中,汽车的尾灯拖曳出红色的线条。 在较老…...
java开发之fastjson
依赖 <!-- fastjson依赖 --> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.76</version> <…...
第一个C语言程序:HelloWorld
第一个C语言程序 注释 注释 对代码的解释和说明 特点 ○ 不会被执行 目的 让人们能够更加轻松地看懂代码 分类 行注释 // 快键键 ctrl/ 块注释 /**/ 快捷键 shiftalta 示例代码: #include <stdio.h>int main() {// 行注释/*块注释*/printf("hello w…...
golang 使用 viper 加载配置文件 自动反序列化到结构
文章博客地址:golang 使用 viper 加载配置 自动反序列化到结构 golang使用 viper 无需设置 mapstructure tag 根据配置文件后缀 自动返序列化到结构解决结构有下划线的字段解析不成功问题 viper 正常加载配置文件 golang viper 其中可以用来 查找、加载和反序列化JSON、TOML…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
《通信之道——从微积分到 5G》读书总结
第1章 绪 论 1.1 这是一本什么样的书 通信技术,说到底就是数学。 那些最基础、最本质的部分。 1.2 什么是通信 通信 发送方 接收方 承载信息的信号 解调出其中承载的信息 信息在发送方那里被加工成信号(调制) 把信息从信号中抽取出来&am…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...
解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
链式法则中 复合函数的推导路径 多变量“信息传递路径”
非常好,我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题,统一使用 二重复合函数: z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y)) 来全面说明。我们会展示其全微分形式(偏导…...
