线程很讨厌的一点,抢占式执行
调度的过程是随机的~~
很多时候,我们希望多个线程按照一个预期的顺序来进行执行 ~
wait notify
就是来调配线程执行顺序的~
线程执行到wait
,就会发生阻塞~~
直到另一个线程,调用notify
把这个wait
唤醒~ 才会继续往下走~
监视器,此处特指的是 synchronized
~ synchronized
也叫做监视器锁
wait
操作,内部本质上,做了三件事 :
1.释放当前锁(保证其他线程能够正常往下运行)~
2.进行等待通知
3.满足一定条件的时候(别人调用notify),被唤醒,然后尝试重新获取锁
等待通知的前提是要先释放锁,而释放锁的前提,是你得加了锁(加上锁,才能谈释放)
脱离 synchronized
使用 wait
会直接抛出异常!
✨ wait
结束等待的条件:
- 其他线程调用该对象的
notify
方法.wait
等待时间超时 (wait
方法提供一个带有timeout
参数的版本, 来指定等待时间).- 其他线程调用该等待线程的
interrupted
方法, 导致wait
抛出InterruptedException
异常.
观察wait()
方法使用:
因为没有调用notify()
,所以线程一直处于阻塞状态~
notify 方 法 是 唤 醒 等 待 的 线 程 ❕ |
- 方法
notify()
也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知,并使它们重新获取该对象的对象锁。- 如果有多个线程等待,则有线程调度器随机挑选出一个呈
wait
状态的线程。(并没有 “先来后到”)- 在
notify()
方法后,当前线程不会马上释放该对象锁,要等到执行notify()
方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
观察notify()
方法使用:
public static void main(String[] args) {
Object object = new Object();
Thread thread = new Thread(()-> {
synchronized (object) {
System.out.println("wait之前");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait 之后");
}
});
Thread thread1 = new Thread(()-> {
synchronized (object) {
System.out.println("notify 之前");
object.notify();
System.out.println("notify 之后");
}
});
thread.start();
thread1.start();
}
要保证加锁的对象和调用wait
的对象得是同一个对象,还要保证,调用的wait
对象和调用notify
的对象也是同一个对象~~
a.wait();
b.notify()
上述代码是无法唤醒a
的wait
的!!!
如果线程1先调用了wait,
线程2后调用notify
,此时notify
会唤醒wait
如果线程2先调用了notify
,线程1后执行了wait
,此时就会错过唤醒wait
!(但如果没人wait,调用notify()
没有副作用)
❗ 所以编写代码的时候要考虑notify
是否会在wait
之前执行的情况,防止wait
没人唤醒,导致一直等待~
wait/notify
控制多线程之间的执行先后顺序~
总结:
- 都要搭配synchronized来进行使用
- 得使用同一个对象,才是有效的
- 用来加锁的对象和wait / notify对象也得一致
- 即使当前没有线程在wait,直接notify也不会有副作用
多个线程都在wait的话,notify
是随机唤醒一个~
而notifyAll
则是全部唤醒!!
但很少用到~
因为即使唤醒了所有的wait
,这些wait
又需要重新竞争锁,重新竞争锁的过程仍然是单行的~~
都是让线程进入阻塞等待的状态
sleep
是通过时间来控制何时唤醒的~~
wait
则是由其他线程通过notify
来唤醒的~
wait
还有一个重载版本,参数可以传时间,表示等待的最大时间(类似于join
)