wait()方法和notify()方法

由于操作系统对线程的调度是随机执行的,且线程之间是抢占式执行的,因此线程之间执行的先后顺序难以预知。但是,有时候在实际开发中,我们希望合理的协调多个线程之间的先后执行顺序。在Java中,wait()方法和notify()方法就是解决该问题的。

1.wait()方法

线程饿死:线程饿死也叫线程饥饿,当多个线程同时竞争一把锁的时候,由于操作系统对线程的调度是随机的,所以当获取锁的线程释放锁之后,接下来哪个线程会拿到锁,这是不确定的。但是由于其他线程都属于在锁上阻塞等待,处于阻塞状态,而当前释放锁的线程处于就绪状态,这个线程还是有很大概率拿到该锁的。这样就会导致其他线程一直吃不到CPU资源,出现线程饿死的现象。

语法形式:

syncronized(锁对象){

       锁对象.wait();

}

虽然我们无法干预调度器对于线程的调度,但是我们可以通过调用wait()方法,然后面的逻辑先执行,等后面的逻辑先执行完之后,当前面的逻辑收到通知,再继续执行。

wait()所作的事情

1.让调用wait方法的线程进行等待(将该线程放到等待队列中)

2.释放当前线程所持有的锁

3.满足一定条件被唤醒之后,线程再此重新获取该锁

当一个线程调用对象的wait方法时,该线程会释放该对象的监视器锁,并进入等待队列中等待被唤醒。

 

注意:由于使用wait()方法会释放锁,所以,再Java中,使用notify()方法之前,我们先加锁,要搭配syncronized使用。否则会报出一个非法锁状态的异常。

如下图

wait()和join()的区别

两个方法都是等,但是join方法必须要等待另一个线程全部执行完之后,代码才能继续走下去,但是wait方法不一定另一个线程全部执行完,只需要下面的逻辑执行到notify方法,代码就可以继续走下去。

syncronized不也是等待吗?

我们要知道,有时尽管写了syncronized,但是它不一定触发等待,因为我们不确定别的线程是否为加锁状态。如果其他线程没有处于加锁状态,那么该线程就直接获取到锁,直接加锁,这个过程中并没有等待。 

wait方法的结束条件:

1.其他线程调用notify方法

2.其他线程调用该线程的interrupted方法,会导致wait抛出InterruptedException异常,导致wait被唤醒,同时也导致该线程结束。

3.wait方法也提供了带参数版本,来指定等待的时间。这时只要等待的时间到了,线程就会自动唤醒,不用notify来通知唤醒。

当代码执行到wait方法后,该线程会一直等待下去,那么我们肯定不能让该线程继续等待下去,这时,我们就要用到notify方法去唤醒线程了。 

2.notify()方法

语法:

syncronized(锁对象){

      锁对象.notify();

}

notify()方法是用来唤醒因为wait方法而阻塞的线程。

注意事项:

1. notify()方法也要搭配syncronized()使用,这是Java中特殊规定的。

2. 使用notify()之前,务必要确保先wait了,否则,notify()方法就没起到唤醒的作用,但是也不会有副作用(抛异常)

3. 如果有多个线程在同一个锁对象上进行了wait,那么,notify()会随机唤醒多个线程中的一个线程。

4. 代码执行到notify()方法后,当前线程并不会马上释放锁,而是要等到执行完notify()方法的线程将程序执行完之后才会释放锁,也就是退出同步代码块(syncronied修饰的代码块)之后才会释放对象锁。

5. wait方法和notify方法锁对象要是一个锁对象,才会起作用

6. 被唤醒的线程不会立即获得对象的监视器锁并继续执行,而是从等待队列中移出,进入与其他线程竞争锁的状态。只有当该线程获得了对象的监视器锁后,才能继续执行wait之后的代码。

例子:

public class Demo9 {public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){System.out.println("t1在wait之前");try {System.out.println("t1执行到wait,释放锁");locker.wait();System.out.println("t1被唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2=new Thread(()->{synchronized (locker){System.out.println("t2获取到锁");try {locker.wait();System.out.println("t2被唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t3=new Thread(()->{synchronized (locker){System.out.println("t3唤醒t1");locker.notify();System.out.println("同步代码块执行完,t3释放锁");}});t1.start();t2.start();t3.start();}
}

根据代码运行结果分析代码逻辑:线程t1启动并获取到锁,进行加锁,接着线程t2也启动,也尝试获取锁,由于t1没有释放锁,t2就阻塞了。接着在线程t1中,执行到wait()方法,t1此时就释放锁,然后线程t3就获取到了锁,当在线程t3中执行到notify()方法,去唤醒t1,当t3中的同步代码块里面的逻辑执行完后,就释放锁 ,由于t1被唤醒需要时间在这段时间内,由于t1没被唤醒,无法获取到锁,线程t2就获取到了锁,线程t2就会执行到wait,wait方法就会导致线程t2释放锁,这回t1就已经被唤醒了,就会获取到锁,线程t1就会继续执行下去。但是线程t2由于没有notify方法去唤醒,所以它就一直处于睡眠状态,不会被唤醒。

2.1notifyAll()

如果我们想要一次唤醒多个线程在同一个锁对象进行wait,我们就可以使用notifyAll()方法。

如修改上面代码,将线程t1和线程t2全部唤醒。

    public static void main(String[] args) {Object locker=new Object();Thread t1=new Thread(()->{synchronized (locker){System.out.println("t1在wait之前");try {locker.wait();System.out.println("t1被notifyAll()唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t2=new Thread(()->{synchronized (locker){System.out.println("t2在wait之前");try {locker.wait();System.out.println("t2被notifyAll()唤醒");} catch (InterruptedException e) {e.printStackTrace();}}});Thread t3=new Thread(()->{Scanner scanner=new Scanner(System.in);System.out.println("请输入唤醒所有线程");scanner.next();synchronized (locker){locker.notifyAll();System.out.println("notifyall方法后");}});t1.start();t2.start();t3.start();}

注意事项:notifyAll()方法在唤醒所有在等待队列中的线程时,这几个线程是存在锁竞争的,只有一个线程能获得锁,其余线程则会继续阻塞,继续尝试获取锁。 

 3.wait()和sleep()的区别

共同点:wait和sleep都能使线程暂停一段时间

不同店:

1. wait使Object类中得一个方法,sleep是Thread类中得一个方法;

2. wait方法必须在syncronized修饰的代码块或方法中使用,sleep方法可以在任何位置使用;

3.wait被调用后,当前线程会进入BLOCK状态并释放锁,并可以通过notify和notifyAll方法进行唤醒,sleep方法被调用后,当前线程会进入TIME_WAITING状态,不涉及相关锁的操作。

1.wait方法需要搭配锁来使用,先加锁,之后才能wait,而sleep使用前不需要加锁。 

2.如果都是在syncronized内部使用,wait会释放锁,而sleep方法不会释放锁。

4.题目

1.有三个线程,分别只能打印A,B,C,按ABC的顺序打印。

public class Demo18 {public static void main(String[] args) throws InterruptedException {Object locker1=new Object();Object locker2=new Object();Object locker3=new Object();Thread t1=new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker1){try {locker1.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("A");synchronized (locker2){locker2.notify();}}});Thread t2=new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker2){try {locker2.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("B");synchronized (locker3){locker3.notify();}}});Thread t3=new Thread(()->{for (int i = 0; i < 10; i++) {synchronized (locker3){try {locker3.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.print("C"+"\n");synchronized (locker1){locker1.notify();}}});t1.start();t2.start();t3.start();Thread.sleep(1000);synchronized (locker1){locker1.notify();}}
}

2.有三个线程,线程名称分别为a,b,c,让每个线程打印自己的名称,并按cba的顺序打印。

public class Demo19 {public static void main(String[] args) throws InterruptedException {Object locker1=new Object();Object locker2=new Object();Object locker3=new Object();Thread a=new Thread(()->{try {synchronized (locker1){locker1.wait();}}catch (InterruptedException e){e.printStackTrace();}System.out.print("a");});Thread b=new Thread(()->{try {synchronized (locker2){locker2.wait();}}catch (InterruptedException e){e.printStackTrace();}System.out.print("b");synchronized (locker1){locker1.notify();}});Thread c=new Thread(()->{try {synchronized (locker3){locker3.wait();}}catch (InterruptedException e){e.printStackTrace();}System.out.print("c");synchronized (locker2){locker2.notify();}});a.start();b.start();c.start();Thread.sleep(1000);//让线程a,b,c进入wait状态synchronized (locker3){locker3.notify();}}
}

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

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

相关文章

搭建 EwoMail 邮件服务器

EwoMail简介 EwoMail是基于Linux的开源邮件服务器&#xff0c;支持一键搭建&#xff0c;集成了众多优秀稳定的组件&#xff0c;是一个快速部署、简单高效、安全稳定的邮件解决方案&#xff0c;支持电脑和手机的客户端&#xff0c;适合个人或邮箱功能需求少的企业。 非常稳定&…

ST算法解RMQ问题

题目 代码 #include <bits/stdc.h> using namespace std; const int N 2e510, M 20; int st[N][M]; int n, m; int main() {ios::sync_with_stdio(0);cin.tie(0);cin >> n;for(int i 1; i < n; i)cin >> st[i][0];for(int i 1; (1 << i) < …

STM32启动文件分析

1. 启动文件简介 启动文件由汇编编写&#xff0c;是系统上电复位后第一个执行的程序。主要做了以下工作&#xff1a; 初始化堆栈指针SP_initial_sp;初始化程序计数器指针PCReset_Handler;设置堆、栈的大小;初始化中断向量表;配置外部SRAM作为数据存储器&#xff08;这个由用户…

Netty 组件介绍 - Future Promise

在异步处理时&#xff0c;经常用到这两个接口 netty 中的 Future 继承 jdk 中的 FutuFuture&#xff0c;而Promise 又对 netty Future 进行了扩展。 idk Future 只能同步等待任务结束&#xff08;或成功或失败)才能得到结果netty Future 可以同步等待任务结束得到结也可以异…

破局智能制造:难点分析与对策

一、 智能制造过程中可能遇到难点: 1. --概念和技术繁多--: - 智能制造领域涉及众多概念和技术,如工业4.0、CPS、工业互联网等,让企业难以选择和应用。 2. --缺乏经验和成功案例--: - 企业在推进智能制造时缺乏经验,存在信息孤岛、自动化孤岛等问题,缺乏统一规划和系统推…

buuctf

就随便刷刷&#xff0c;就不写那么详细啦&#xff0c;就写写我的一些收获和不懂的地方啦 1. mb_substr($page&#xff0c;n&#xff0c;m)&#xff1a;返回page中从第n位开始&#xff0c;到nm位字符串的值 这个我觉得就是从第一个问号的地方开始截取&#xff0c;然后截到第二个…

AIGC实战——生成式人工智能总结与展望

AIGC实战——生成式人工智能总结与展望 0. 前言1. 生成式人工智能发展历程1.1 VAE 和 GAN 时代1.2 Transformer 时代1.3 大模型时代 2. 生成式 AI 的当前进展2.1 大语言模型2.2 文本生成代码模型2.3 文本生成图像模型2.4 其他应用 3. 生成式人工智能发展展望3.1 生成式 AI 在工…

分数阶傅里叶变换与信息熵怎么用于信号处理?

天马行空的理解与思考方式&#xff1a;分数阶傅里叶变换与信息熵怎么用于信号处理&#xff1f; ChiX-Y 快速学习&#xff0c;快速尝试&#xff0c;快速失败 已关注 35 人赞同了该文章 这篇文章希望能写的有趣&#xff0c;同时有质量&#xff0c;学习就是要多维度多角度&…

深入理解C++ Lambda表达式:语法、用法与原理及其包装器的使用

深入理解C Lambda表达式&#xff1a;语法、用法与原理及其包装器的使用 lambda表达式C98中的一个例子lambda表达式语法lambda表达式各部分说明捕获列表说明 函数对象与lambda表达式 包装器function包装器 bind &#x1f30f;个人博客主页&#xff1a; 个人主页 本文深入介绍了…

前端请求后端接口报错(blocked:mixed-content),以及解决办法

报错原因&#xff1a;被浏览器拦截了&#xff0c;因为接口地址不是https的。 什么是混合内容&#xff08;Mixed Content&#xff09; 混合内容是指在同一页面中同时包含安全&#xff08;HTTPS&#xff09;和非安全&#xff08;HTTP&#xff09;资源的情况。当浏览器试图加载非…

Diving into the STM32 HAL-----Interrupts

硬件管理就是处理异步事件。其中大部分来自硬件外围设备。例如&#xff0c;计时器达到配置的 period 值&#xff0c;或者 UART 在数据到达时发出警告。 中断是一个异步事件&#xff0c;它会导致按优先级停止执行当前代码&#xff08;中断越重要&#xff0c;其优先级越高;这将导…

linux操作系统进程

linux操作系统是对下的软硬件进行管理&#xff0c;为了能够对上提供稳定&#xff0c;快速&#xff0c;安全的服务而诞生的软件。 广义上的操作系统是包含搭载在操作系统上的软件和函数库等文件的。 狭义上的操作系统就是操作系统内核&#xff0c;进行进程管理&#xff0c;文件…

js 获取当前时间与前一个月时间

// 获取当前时间的毫秒数 var currentTimeMillis new Date().getTime();// 获取前一个月的Date对象 var dateLastMonth new Date(); dateLastMonth.setMonth(dateLastMonth.getMonth() - 1);// 获取前一个月的毫秒数 var timeMillisLastMonth dateLastMonth.getTime();conso…

php内置服务停止shell小工具,用来停止指定的端口的php内置服务进程

最近vscode总是喜欢闪退&#xff0c;这导致了上面启动的php内置服务变成了无法管理状态&#xff0c;所以就有了这个工具来停止相关的PHP内置服务进程. 将下面的代码保存到本地合适的位置&#xff0c;并命名为 stop.sh #!/bin/bash # Author: tekintian # Date: 2024-11-02 …

服务器文件访问协议

服务器文件访问协议 摘要NFS、CIFS、SMB概述SMBWindows SMBLinux SambaPython SMB NFS 摘要 本篇博客参考网上文档和博客&#xff0c;对基于网络的服务器/主机的文件访问、共享协议进行简要总结&#xff0c;完整内容将会不断更新&#xff0c;以便加深理解和记忆 NFS、CIFS、S…

Leetcode - 周赛421

目录 一&#xff0c;3334. 数组的最大因子得分 二&#xff0c;3335. 字符串转换后的长度 I 三&#xff0c;3336. 最大公约数相等的子序列数量 四&#xff0c;3337. 字符串转换后的长度 II 一&#xff0c;3334. 数组的最大因子得分 暴力方法就不演示&#xff0c;这里介绍一个…

【java】java的基本程序设计结构06-运算符

运算符 一、分类 算术运算符关系运算符位运算符逻辑运算符赋值运算符其他运算符 1.1 算术运算符 操作符描述例子加法 - 相加运算符两侧的值A B 等于 30-减法 - 左操作数减去右操作数A – B 等于 -10*乘法 - 相乘操作符两侧的值A * B等于200/除法 - 左操作数除以右操作数B /…

群控系统服务端开发模式-应用开发-菜单功能开发

为什么优先开发菜单&#xff0c;而不是优先开发管理员&#xff1f;查看一下程序草图就明白&#xff0c;还有一个重点就是&#xff0c;管理员需要添加图片&#xff0c;而我还没有封装上传工具及上传目标。 一、添加路由 在根目录下route文件夹下的app.php文件里面&#xff0c;添…

顶点动画-河流的效果

目标是让一个矩形网格面片&#xff0c;通过顶点动画&#xff0c;实现出河流的效果。&#xff08;如下图&#xff09;所谓的河流效果&#xff0c;就是呈现出波浪感&#xff0c;而想要呈现出波浪感&#xff0c;我们必须了解 波长、波动频率、波动幅度 这些关键因素 1、波浪感的关…

线程函数和线程启动的几种不同形式

线程函数和线程启动的几种不同形式 在C中&#xff0c;线程函数和线程启动可以通过多种形式实现。以下是几种常见的形式&#xff0c;并附有相应的示例代码。 1. 使用函数指针启动线程 最基本的方式是使用函数指针来启动线程。 示例代码&#xff1a; #include <iostream&g…