目录
一、wait和notify/notifyAll的由来
二、wait()方法
三、notify方法
3.1 notify的作用
3.2 wait和notify的 相互转换代码+图
3.3 notifyAll
四、为什么需要notify和wait都需要上锁?
五、wait和sleep的对比
前言:由于线程之间是抢占式执行的,因此线程之间的先后顺序难以判断,具有很强的”随机性“(ps:这里的随机并非数学上的随机,但确实是无法判断的)。
一、wait和notify/notifyAll的由来
Java为了保证实际开发中可以合理的协调各个线程执行的先后顺序,引入了三个方法。
- wait() / wait(long timeout) : 让当前线程进入等待状态。
- notify() / notifyAll() :唤醒在当前对象上等待的线程。
注意:wait ,notify,notifyAll 都是 Object 类的方法。
二、wait()方法
wait方法执行之后做了啥?其作用是什么?
- 使当前执行代码的线程进行等待。(把线程放在等待队列中)
- 释放当前锁(这需要调用wait的线程是加锁状态,这里的释放是指先暂停对Synchronized所修饰的代码块的运行,后续在重新获取锁的占有权后继续执行wait代码后面的部分)。
- 满足一定条件时,让使用wait的线程被唤醒(其他线程中调用notify/notifyAll,或者代码抛出InterruptedException,线程宕掉等),需要重新获取这个锁(需要参与锁竞争)。
作用:当前线程因为某些原因需要阻塞等待,但是又不能影响后续线程的工作,于是需要调用wait方法,当阻塞完毕后(当前线程被唤醒),需要继续参与锁竞争后才能拿到当前锁。
下面我们来看以下代码:
ps:synchronized 在JVM中 也叫做监控器锁。
这也正印证了我们上面所讲解的第二点:wait在运行的过程中会 释放当前锁,
换句话来说: wait要搭配 synchronized 来使用,脱离synchronized使用wait会直接抛出异常。
于是我们将上述代码进行调整:
观察代码发现,object.wait() 之后就一直处于等待状态。这时候我们就需要notify方法来将其唤醒。
三、notify方法
3.1 notify的作用
唤醒等待中的线程。
- notify是包含在Synchronized中的。
- 线程1没有释放锁的情况下,线程2是无法调用notify(因为需要阻塞等待)
- 线程1调用wait,在wait里面释放了锁(wait的功能就包含了释放锁,这个时候虽然线程1的代码运行的部分还在synchronized里面,但是锁处于释放状态),线程2才可以拿到锁。
- 如果是多个线程等待,则就会发生锁竞争:即使线程1刚刚被唤醒了(之前拿到过锁),也还是要参加锁竞争。
- 即使线程2调用notify后,线程1或者其他线程也不能马上拿到锁,而是需要等待notify方法的线程将程序执行完才行,即 线程2释放锁之后才行。
总结:要保证加锁的对象和调用wait的对象是同一个对象,调用wait的对象和调用notify的对象也是同一个对象。
3.2 wait和notify的 相互转换代码+图
例如:a.wait() 使用b.notify() 是无法将其唤醒的.(ps:这里的a,b都是对象名,非线程名)
这里将展示代码和画流程图来帮助理解:
public class Demo15 {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
//第一个线程进行wait操作
Thread t1 = new Thread(()-> {
while(true) {
System.out.println("wait 之前");
synchronized (object) {
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait之后");
}
}
});
t1.start();
Thread.sleep(5000);
Thread t2 = new Thread(()-> {
while(true) {
System.out.println("notify 之前");
synchronized (object) {
object.notify();
System.out.println("notify之后");
}
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t2.start();
}
}
结果:
配合流程图食用效果最佳:
理解了notify之后,再来看notifyAll就很轻松了:
3.3 notifyAll
Java中除了有一个notify之外,还有一个notifyAll功能。多线程都执行了wait后,某个线程调用了notify,则是随机唤醒一个,如果是调用notifyAll就是将这些线程全部唤醒(当然了,他们都要参与锁竞争才能拿到锁,也就是说他们的执行顺序并不是并行而是串行的)。
四、为什么需要notify和wait都需要上锁?
我们发现无论是wait方法还是notify方法都必须处于synchronized所修饰的代码块内。这是为什么呢?
这其实是因为会造成丢失唤醒问题。
何为丢失唤醒问题?
就是在线程1执行wait之前,线程2执行了notify方法,提前将一个”醒“着的线程”唤醒“了。(这时的执行notify是没有产生任何作用的)。
举个例子:
分析:
加了synchronized修饰后,线程1只要先拿到锁 (因为锁有独占性),这时候,线程2就不会出现在线程1还没有执行wait之前就调用notifv的可能性,这也正是为什么notify和synchronized要加锁的原因(因为两个加锁才能产生锁竞争,才能保证代码正确的运行)
五、wait和sleep的对比
其实理论上 wait 和 sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻塞一段时间,
唯一的相同点就是都可以让线程放弃执行一段时间。
总结:
- wait需要搭配synchronized使用,sleep不需要。
- wait是Object的方法,sleep是Thread的静态方法。