多线程和并发(1)—等待/通知模型

一、进程通信和进程同步

1.进程通信的方法

同一台计算机的进程通信称为IPC(Inter-process communication),不同计 算机之间的进程通信被称为 RPC(Romote process communication),需要通过网络,并遵守共同的协议。**进程通信解决的问题是两个或多个进程间如何交换数据的问题。**常用的进程通信的方法如下:

  1. 管道:分为匿名管道(pipe)及命名管道(named pipe):匿名管道可用 于具有亲缘关系的父子进程间的通信,命名管道除了具有管道所具有的功能外, 它还允许无亲缘关系进程间的通信。
  2. 信号(signal):信号是在软件层次上对中断机制的一种模拟,它是比较 复杂的通信方式,用于通知进程有某事件发生,一个进程收到一个信号与处理器 收到一个中断请求效果上可以说是一致的。
  3. 消息队列(message queue):消息队列是消息的链接表,它克服了上两 种通信方式中信号量有限的缺点,具有写权限得进程可以按照一定得规则向消息 队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息。 4. 共享内存(shared memory):可以说这是最有用的进程间通信方式。它 使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共 享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。
  4. 信号量(semaphore):主要作为进程之间及同一种进程的不同线程之间的同步和互斥手段。
  5. 套接字(socket):这是一种更为一般得进程间通信机制,它可用于网络 中不同机器之间的进程间通信,应用非常广泛。同一机器中的进程还可以使用 Unix domain socket(比如同一机器中 MySQL 中的控制台 mysql shell 和 MySQL 服 务程序的连接),这种方式不需要经过网络协议栈,不需要打包拆包、计算校验 和、维护序号和应答等,比纯粹基于网络的进程间通信肯定效率更高。

2.线程同步的方法

**线程同步解决的问题是多个线程在并发执行过程中需要保持数据一致性和顺序性等问题。**常见的线程同步方法如下:

  1. 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
  2. 互斥量:为协调共同对一个共享资源的单独访问而设计的。互斥量跟临界区很相似,比临界区复杂,互斥对象只有一个,只有拥有互斥对象的线程才具有访问资源的权限。
  3. 信号量:为控制一个具有有限数量用户资源而设计。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。
  4. 事件: 用来通知线程有一些事件已发生,从而启动后继任务的开始。

二、创建线程的方法

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下所示:

  1. 继承Thread类创建线程
  2. 实现Runnable接口创建线程
  3. 使用Callable和Future创建线程
  4. 使用线程池例如用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任务。

3D8020B4-B418-4DD0-BE90-0B75DB4AA5DB

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

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:

  1. 创建Callable接口的实现类,并实现call()方法,然后创建该实现类的实例(从java8开始可以直接使用Lambda表达式创建Callable对象)。
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用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 {
    @Override
    public 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{
    @Override
    public 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{
    @Override
    public 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 {

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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() {
            @Override
            public 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() {
            @Override
            public 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 发布!

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

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

相关文章

什么是 API ?

一、API 的定义&#xff1a;数据共享模式定义 4 大种类 作为互联网从业人员&#xff0c;API 这个词我耳朵都听起茧子了&#xff0c;那么 API 究竟是什么呢&#xff1f; API 即应用程序接口&#xff08;API&#xff1a;Application Program Interface&#xff09;&#xff0c;…

对1GHz脉冲多普勒雷达进行快速和慢速处理生成5个移动目标的距离多普勒图研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

代码随想录算法训练营第四十六天|139.单词拆分、多重背包、背包问题总结

139.单词拆分 ★ 文档讲解 &#xff1a; 代码随想录 - 139.单词拆分 状态&#xff1a;再次回顾。&#xff08;★&#xff1a;需要多次回顾并重点回顾&#xff09; 本题其实不套完全背包思路来理解反而更简单易懂一点。 动态规划五部曲&#xff1a; 确定dp数组&#xff08;dp ta…

以getPositionList为例,查找接口函数定义及接口数据格式定义

job-app-master/pages/index/index.vue中299行 async getPositionList(type refresh, pulldown false) {this.status 请求中;if (type refresh) {this.query.page 1;} else {this.query.page;}let res await this.$apis.getPositionList(this.query);if (res) {if (type …

福利之舞:员工的心跳与企业的平衡术

引言&#xff1a;员工福利与满意度的关系 在现代企业中&#xff0c;员工福利已经不仅仅是一种待遇&#xff0c;而是与员工满意度、忠诚度和生产力紧密相连的关键因素。一个合理且吸引人的福利制度可以大大提高员工的工作积极性&#xff0c;同时也能够吸引和留住顶尖的人才。但…

.NET 最便捷的Log4Net日志记录器

最便捷的Log4Net使用方法 LOG4NET 配置日志记录器开始引用nuget LOG4NET 配置日志记录器 Apache log4net 库是一个帮助程序员将日志语句输出到各种的工具 的输出目标。log4net是优秀的Apachelog4j™框架的移植 Microsoft.NET 运行时。我们保持了与原始log4j相似的框架 同时利…

Linux CentOS7系统,抓取http协议的数据包

使用 tcpdump 命令 1.首先确认是否安装 [rootlocalhost ~]# which tcpdump /usr/bin/which: no tcpdump in (/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin) [rootlocalhost ~]#我这里没有安装 1.1 安装 tcpdump yum install tcpdump 安装成功如下&#xf…

Linux网络编程:多路I/O转接服务器(select poll epoll)

文章目录&#xff1a; 一&#xff1a;select 1.基础API select函数 思路分析 select优缺点 2.server.c 3.client.c 二&#xff1a;poll 1.基础API poll函数 poll优缺点 read函数返回值 突破1024 文件描述符限制 2.server.c 3.client.c 三&#xff1a;epoll …

OpenCV + CLion在windows环境下使用CMake编译, 出现Mutex相关的错误的解决办法

最近在windows下面用cmake编译OpenCV的项目代码,但是一直碰到找不到mutex的问题&#xff0c;百思不得其解, Executing task: g -g -o bin/debug.exe src/main.cppC:\MinGW\lib\opencv\build\include/opencv2/core/utility.hpp:697:14: error: recursive_mutex in namespace st…

智慧能源管理系统助力某制造企业提高能源利用效率

随着全球能源需求不断增加和能源价格的上涨&#xff0c;企业和机构日益意识到能源管理的重要性。传统的能源管理方式不仅效率低下&#xff0c;还容易造成资源浪费和环境污染。因此&#xff0c;许多企业开始探索采用智慧能源管理系统来提高能源利用效率&#xff0c;降低能源成本…

9. 解谜游戏

目录 题目 Description Input Notes 思路 暴力方法 递归法 注意事项 C代码&#xff08;递归法&#xff09; 关于DFS 题目 Description 小张是一个密室逃脱爱好者&#xff0c;在密室逃脱的游戏中&#xff0c;你需要解开一系列谜题最终拿到出门的密码。现在小张需要打…

Linux:Nginx服务与搭建

目录 一、Nginx概述 二、Nginx三大作用&#xff1a;反向代理、负载均衡、动静分离 三、Nginx和Apache 3.1Nginx和Apache的差异 3.2Nginx和Apache的优缺点比较 四、编译安装niginx 五、创建Nginx 自启动文件 六、Nginx的信号使用 6.1信号 七、升级 nginx1.18 nginx1.2…

项目---日志系统

目录 项目系统开发环境核心技术日志系统介绍为什么需要日志系统? 日志系统框架设计日志系统模块划分代码实现通用工具实现日志等级模块实现日志消息模块实现格式化模块实现落地模块实现日志器模块同步日志器异步日志器缓冲区实现异步工作器实现 回归异步日志器模块建造者模式日…

前端需要理解的工程化知识

1 Git 1.1 Git 常见工作流程 Git 有4个区域&#xff1a;工作区&#xff08;workspace)、index&#xff08;暂存区&#xff09;、repository&#xff08;本地仓库&#xff09;和remote&#xff08;远程仓库&#xff09;&#xff0c;而工作区就是指对文件发生更改的地方&#xff…

Android View动画整理

View 动画相关内容可参考官网 动画资源 此前也有写 View 动画相关的内容&#xff0c;但都只是记录代码&#xff0c;没有特别分析。以此篇作为汇总、整理、分析。 Android View 动画有4中&#xff0c;分别是 平移动画 TranslateAnimation缩放动画 ScaleAnimation旋转动画 Rot…

大数据时代的软件开发实践:利用云计算和AI赋能创新

文章目录 云计算的赋能弹性资源管理远程协作与分布式开发持续集成和持续交付成本效益 人工智能的赋能数据驱动的决策自动化智能预测和优化自适应系统 创新的实践方法数据驱动的创新智能化产品开放式创新迭代和反馈 &#x1f388;个人主页&#xff1a;程序员 小侯 &#x1f390;…

VBJSON报错:缺少:语句结束

项目中使用JSON库VBJSON时报错&#xff1a; 编译错误&#xff1a;缺少&#xff1a;语句结束 cJSONScript和cStringBuilder报相同的错误&#xff0c;都在第一行: VERSION 1.0 CLASS 研究了半天没啥结果&#xff0c;之前使用这个库的时候没有什么问题&#xff0c;所以判定是当前…

yolov8实战之torchserve服务化:使用yolov8x来预打标

前言 最近在做一个目标检测的任务&#xff0c;部署在边缘侧&#xff0c;对于模型的速度要求比较严格&#xff08;yolov8n这种&#xff09;&#xff0c;所以模型的大小不能弄太大&#xff0c;所以原模型的性能受限&#xff0c;更多的重点放在增加数据上。实测yolov8x在数据集上…

【C/C++】父类指针指向子类对象 | 隐藏

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

(四)CUDA应用程序编程接口详解

C语言扩展 CUDA的编程接口是C语言的扩展集&#xff0c;其中主要的是Runtime库&#xff0c;该库分为三个组件&#xff1a;主机组件、设备组件以及公共组件 主机组件&#xff1a;在主机上运行并提供函数来控制和访问一个或多个计算设备 设备组件&#xff1a;设备运行并且提供特…