前言
我们线程的执行是抢占式的,线程的执行是我们不可预测的,但实际的开发中,我们希望合理的协调多个线程同时进行,但是要怎么办呢?于是我们就引入这个wait和notify的方法,接下来我们来介绍这俩个方法的应用场景和具体是怎么使用的.
一.wait方法
wait方法要做的事情:
使当前执行代码的线程进行等待. (把线程放到等待队列中)
释放当前的锁
满足一定条件时被唤醒, 重新尝试获取这个锁.
这样吧,我们来个例子来说明一下:
public class ThreadDemo20 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
System.out.println("wait 之前");
synchronized (object) {
object.wait();
}
System.out.println("wait 之后");
}
}
当然这个程序运行起来之后,你会觉得很疑惑
实际上就是没有结束的,它相当于一直让线程进入了阻塞状态,当然我们线程必须有结束条件的.
结束条件:
其他线程调用该对象的 notify 方法.
wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.
当然我们先不对这个程序进行优化,先不使用notify方法,等我们介绍完notify方法是什么以后,然后对上述程序加一个notify方法.
二.notify方法
这里大概的列出它的方法概述:
方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的
其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行
完,也就是退出同步代码块之后才会释放对象锁。
可能上面说了一大堆,你也没怎么明白,是怎么回事,我们来一个实际的生活例子,你就应该会明白了.
当然这个例子可能还是不好,但在后面我们会继续用一些实际性的例子,去让你慢慢明白的,不用着急.
接下来,要回到我们刚开始的那一个程序,我们要让wait结束,并唤醒,看完我接下来写的代码,或许你会有一定的思路.
public class ThreadDemo21 {
public static void main(String[] args) throws InterruptedException {
Object locker =new Object();
Thread t1=new Thread(() ->{
try {
System.out.println("wait 开始");
synchronized (locker){
locker.wait();
}
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
});
t2.start();
}
}
当然执行结果如下图所示:
那如果我们的t1线程比t2线程后执行呢?那又会是什么结果呢?
大家看我用文字描述一番
如果线程t2先于线程t1运行,并调用了notify方法,但此时线程t1还没有调用wait方法,那么notify方法会没有任何效果,线程t1仍然会进入等待状态。输出结果如下:
wait 开始
notify 开始
notify 结束
代码更改就不展示了.因为只是线程的执行顺序不同,但执行效果如下:
从这里我们就可以发现,notify实际上是要和wait搭配使用的.如果没有陷入阻塞等待的线程,notify也不会报出异常,wait方法将一直等待下去,所以在这方面,我们还是要谨慎的.介绍完notify以后,我们还有一个notifyAll方法没有解释.
三.notifyAll方法
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
我们接上文的例子,重新对原来的例子进行一些改进,看效果如下:
public class ThreadDemo22 {
public static void main(String[] args) throws InterruptedException {
Object locker =new Object();
Thread t1=new Thread(() ->{
try {
System.out.println("wait 开始");
synchronized (locker){
locker.wait();
}
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t3=new Thread(() ->{
try {
System.out.println("wait 开始");
synchronized (locker){
locker.wait();
}
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t4=new Thread(() ->{
try {
System.out.println("wait 开始");
synchronized (locker){
locker.wait();
}
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("notifyAll 开始");
locker.notifyAll();
System.out.println("notifyAll 结束");
}
});
t1.start();
t3.start();
t4.start();
Thread.sleep(1000);
t2.start();
}
}
执行结果:
此时可以看到, 调用 notifyAll 能同时唤醒 3 个wait 中的线程
当然特别注意,我们这里: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行.
比如那下面的图示举例子:
notify只唤醒等待队列中的一个线程. 其他线程还是乖乖等着
notifyAll 一下全都唤醒, 需要这些线程重新竞争锁
到这里大家先别急,我们先把这个篇文章结束,接下来的知识,我们在下一篇文章继续循序渐进