多线程-线程通讯线程的通知和唤醒wait()、notify()/notifyAll()
由于多个线程之间是抢占执行的,因此线程之间执行的先后顺序难以预知,但我们在实际的开发过程中,我们希望能够合理的协调多个线程之间的执行先后顺序。
这就引出了完成这个协调工作的三个方法:
- wait()/wait(long timeout):让当前线程进入等待状态。
- notify():唤醒在当前对象上等待的一个线程
- notifyAll():唤醒在当前对象上等待的所有线程。
一、wait()方法的使用
wait()执行流程:
- 使当前线程进入休眠状态(将线程放入等待队列中)
- 释放当前的锁
- 满足一定条件时被唤醒,重新尝试获取这个锁
wait要搭配synchronized来使用,如果没有配合synchronized使用wait会直接抛出异常。
wait()结束等待的条件:
- 其他线程调用该对象的notify方法。
- wait(long timeout)等待超时。
- 其他线程调用该等待线程的interrupted方法,导致wait抛出InterruptedException异常。
/**
* wait使用
*/
public class waitDemo {
public static void main(String[] args) {
Object lock=new Object();
Thread t1=new Thread(()->{
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法...");
//无限期等待
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完毕");
},"线程1");
t1.start();
}
}
这样线程就会一直等待下去,所以我们需要用到唤醒方法notify()。
二、wait(long timeout)
给wait方法传入一个参数,表示等待的时间,如果超过这个时间wait就会结束等待,继续执行任务,而无参的wait方法就会一直等待,直到有线程唤醒它。
我们来看具体的代码执行效果:
import java.time.LocalDateTime;
public class waitDemo4 {
public static void main(String[] args) {
Object lock=new Object();
Object lock2=new Object();
new Thread(()->{
synchronized (lock){
System.out.println("线程1:开始执行");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1:执行完成");
}
},"无参wait线程").start();
new Thread(()->{
synchronized (lock2){
System.out.println("线程2:开始执行" "|开始时间:" LocalDateTime.now());
try {
lock2.wait(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完毕" "|结束时间:" LocalDateTime.now());
}
},"有参wait线程").start();
}
}
可以看到,无参的wait()方法在一直等待,而有参的wait()在等待超时后,会继续往下执行。
三、wait() VS wait(long timeout)
共同点:
- 无论是有参还是无参的wait()方法,都可以使线程进入休眠状态。
- 无论是有参还是无参的wait()方法,都可以使用notify/notifyAll来进行唤醒。
不同点:
- wait(long timeout)当线程超过设置时间后,会自动恢复执行;wait()会无限等待。
- 使用无参的wait方法,线程会进行waiting状态;使用有参的wait方法会进入timeid_waiting状态。
四、notify()方法的使用
notify方法是唤醒等待的线程。
- 方法notify()也需要在同步方法或者同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其他线程,对其发出notify通知,并使他们重新获取该对象的对象锁。
- 在notify()方法后,当前线程并不会立刻释放该对象锁,而是等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放该锁。
public class waitDemo2 {
public static void main(String[] args) {
Object lock=new Object();
Object lock2=new Object();
Thread t1=new Thread(()->{
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法...");
//无限期等待
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完毕");
},"线程1");
t1.start();
Thread t2=new Thread(()->{
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法...");
//无限期等待
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完毕");
},"线程2");
t2.start();
Thread t3=new Thread(()->{
System.out.println("线程3开始执行");
try {
synchronized (lock) {
System.out.println("线程3调用wait方法...");
//无限期等待
lock.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程3执行完毕");
},"线程3");
t3.start();
Thread t4=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4:开始执行,唤醒线程");
synchronized (lock){
//发出唤醒通知
lock.notify();
System.out.println("线程4:执行唤醒操作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4:执行完成");
}
},"线程4");
t4.start();
}
}
从上面代码的执行结果来看,可以说明我们上面提到的几个问题:①notify是可以唤醒等待当前对象的对象锁一个线程 ②在唤醒其他线程之后,并不会立刻释放锁,而是等待这个唤醒线程中的程序执行完之后才释放锁。
四、notifyAll()方法的使用
使用notifyAll()可以一次唤醒所有等待线程。
上代码看看它的使用~
public class waitDemo3 {
public static void main(String[] args) {
Object lock=new Object();
Object lock2=new Object();
Thread t1=new Thread(()->{
System.out.println("线程1开始执行");
try {
synchronized (lock) {
System.out.println("线程1调用wait方法...");
//无限期等待
lock.wait();
System.out.println("线程1:恢复执行之后又进入休眠状态");
Thread.sleep(1000);
System.out.println("线程1执行完毕");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程1");
t1.start();
Thread t2=new Thread(()->{
System.out.println("线程2开始执行");
try {
synchronized (lock) {
System.out.println("线程2调用wait方法...");
//无限期等待
lock.wait();
System.out.println("线程2:恢复执行之后又进入休眠状态");
Thread.sleep(1000);
System.out.println("线程2执行完毕");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程2");
t2.start();
Thread t3=new Thread(()->{
System.out.println("线程3开始执行");
try {
synchronized (lock2) {
System.out.println("线程3调用wait方法...");
//无限期等待
lock2.wait();
System.out.println("线程3:恢复执行之后又进入休眠状态");
Thread.sleep(1000);
System.out.println("线程3执行完毕");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程3");
t3.start();
Thread t4=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4:开始执行,唤醒线程");
synchronized (lock){
//发出唤醒通知
lock.notifyAll();
System.out.println("线程4:执行唤醒操作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4:执行完成");
}
},"线程4");
t4.start();
}
}
通过上面的代码执行结果,我们可以确定notifyAll()可以一次唤醒等待当前对象的对象锁的所有线程。
五、为什么wait和notify必须放在synchronized中执行?
wait和notify如果不配合synchronized使用,在程序运行时就会报IllegalMonitorStateException非法的监视器状态异常,而且notify也不能实现程序的唤醒功能了。
public class waitDemo2 {
public static void main(String[] args) {
Object lock=new Object();
Object lock2=new Object();
Thread t1=new Thread(()->{
System.out.println("线程1开始执行");
try {
System.out.println("线程1调用wait方法...");
//无限期等待
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完毕");
},"线程1");
t1.start();
Thread t4=new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4:开始执行,唤醒线程");
synchronized (lock){
//发出唤醒通知
lock.notify();
System.out.println("线程4:执行唤醒操作");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程4:执行完成");
}
},"线程4");
t4.start();
}
}
因此我们需要配合synchronized使用,Java这样设计是为了防止多线程并发执行时,程序执行混乱问题。所以java引入了锁来防止混乱问题。
那么混乱问题是什么呢?
我们来看下面这个阻塞式队列的执行状况吧~
public class ThreadQueue {
static class MyBlockedQueue{
private int first;//队头
private int last;//队尾
private int size;//队列实际存储大小
private int[] item;//实际存储容器
public MyBlockedQueue(int capacity){
this.first=0;
this.last=0;
this.size=0;
this.item=new int[capacity];
}
/**
* 入队
*/
public void offer(int val) throws InterruptedException {
if(size==item.length){//当前队列已满,阻塞等待,当有元素出队时再唤醒继续执行
this.wait();
}
item[last]=val;
last ;
size ;
//判断是否最后一个元素
if(last==item.length-1){
last=0;
}
this.notify(); //③ 唤醒出队任务中的休眠线程
}
/**
* 出队
*/
public int poll() throws InterruptedException {
int result=0;
if(size==0){// ① 队列中已经没有数据了,阻塞等待入队任务中有元素入队再唤醒继续执行
this.wait(); //②
}
result=item[first];
first ;
size--;
//判断是否最后一个数据
if(first==item.length-1){
first=0;
}
this.notify();//唤醒入队列中的休眠线程
return result;
}
}
}
上面的代码中我们标识了三个重要的步骤,①判断当前队列中有无元素,若没有 执行②wait()方法等待 ,再执行③给队列中添加元素后执行唤醒操作。
但是如果不强制添加synchronized,就有可能出现以下问题:
看出问题了吗?友友们!如果不配合synchronized使用,那么线程1执行完判断之后,还没有进入休眠状态,这时时间片轮转,线程2将元素入队列,并唤醒线程1。时间片再给到线程1时,线程1已经执行过判断了,会直接进入休眠状态,从而导致队列中的那条数据就会永久性的不能被读取。这就是程序并发执行时产生的“执行混乱问题”。
但是如果配合了synchronized使用,执行流程就变成如下这样:
这样子,步骤①②一起执行,程序的执行结果就正常了,这就是我为什么java设计需要让wait和notify必须配合synchronized一起执行的原因。
六、notify真的是随机唤醒吗?
首先我们来说结论~
notify的唤醒并不是随机的,而是从等待队列中获取第一个元素进行出队,也就是顺序唤醒的。
我们来看下notify的源码:
翻译下上面的内容,就是说notify选择唤醒的线程是任意的,但是具体的实现还是要依赖于JVM厂商。
最主流的就是官方的JVM,也就是HotSpot虚拟机。他的源码实现在ObjectMonitor.cpp 中,具体源码如下:
DequeueWaiter 方法实现的源码如下:
所以这也就验证了我们上面所说的,notify的唤醒并不是随机的,而是在——waitSet中获取第一个元素进行出队唤醒。
这就是线程通讯部分的内容啦,小伙伴觉得对你有用的话就点个赞支持一下吧~
这篇好文章是转载于:学新通技术网
- 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
- 本站站名: 学新通技术网
- 本文地址: /boutique/detail/tanhhahgff
-
photoshop保存的图片太大微信发不了怎么办
PHP中文网 06-15 -
word里面弄一个表格后上面的标题会跑到下面怎么办
PHP中文网 06-20 -
《学习通》视频自动暂停处理方法
HelloWorld317 07-05 -
photoshop扩展功能面板显示灰色怎么办
PHP中文网 06-14 -
Android 11 保存文件到外部存储,并分享文件
Luke 10-12 -
微信公众号没有声音提示怎么办
PHP中文网 03-31 -
TikTok加速器哪个好免费的TK加速器推荐
TK小达人 10-01 -
excel下划线不显示怎么办
PHP中文网 06-23 -
excel打印预览压线压字怎么办
PHP中文网 06-22 -
怎样阻止微信小程序自动打开
PHP中文网 06-13