由于线程之间是 抢占式执行的,因此 线程之间执行的先后顺序难以预知. 但是实际开发中有时候我们希望 合理的协调多个线程之间的执行先后顺序。join()方法也可以一定程度控制线程执行的顺序,但是 join()只能,而 wait()方法就能的控制线程的顺序,让线程之间更好的协作,从而完成需求。
代码演示
我们先看一段示例代码
package waitAndnotify;
public class TestDemo {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
System.out.println("wait 之前");
o.wait();
System.out.println("wait 之后");
}
}
运行结果~~
wait 之前
Exception in thread "main" java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at waitAndnotify.TestDemo.main(TestDemo.java:7)
Process finished with exit code 1
当我们执行时,好像并没有达到我们预期的等待效果,而是报了一个IllegalMonitorStateException
非法的监视器状态异常,也就是非法的异常.
要想了解这里为什么报错,我们就要清楚wait的执行过程和特性
注意: wait, notify, notifyAll 都是 Object 类的方法.
wait() - 阻塞等待
wait()方法内部作了什么工作:
- 当前的锁.
- 让线程进入.
- 满足一定条件时被.
- .
wait() 结束等待的条件:
- 其他线程调用该对象的 方法. .
- wait 等待 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间). .
- 其他线程调用该等待线程的 方法, 导致 wait 抛出 InterruptedException 异常…
到此,我们就知道了示例代码为什么会抛出IllegalMonitorStateException
异常,我们将代码进行调整:
package waitAndnotify;
// wait - > Object 类的对象
public class Demo01_wait {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
synchronized (o) {
System.out.println("wait 之前");
// 将wait放到synchronized中,保证以及加锁
o.wait();
System.out.println("wait 之后");
}
}
}
notify() - 通知唤醒
notify 方法是唤醒等待()的线程.
- 方法notify()也要在同步方法或同步块,该方法是用来通知那些可能等待该对象的对象锁。其它线程,对其发出通知notify,并使它们。
- 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”,)
- 在notify()方法后,当前线程,要等到执行notify()方法的线程将程序执行完,也就是。
我们通过代码来观察一下wait()和notify()的执行。
public class waitDemo {
public static void main(String[] args) {
Object object = new Object();
Thread t1 = new Thread(() -> {
synchronized (object) {
System.out.println("wait 之前");
try {
object.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("wait 之后");
}
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (object) {
System.out.println("进行通知");
object.notify();
}
});
t1.start();
t2.start();
}
}
执行结果~~
wait 之前
进行通知
wait 之后
进程已结束,退出代码为 0
在这个代码中,t1 线程启动后,输出“wait之前,然后加锁并且遇到wait()方法,使t1线程进入阻塞状态并且释放锁,t2线程在水面3000ms后,由于t1线程已经释放锁,t2线程就可以拿到锁从而执行到notify()方法唤醒t1线程,输出“进行通知”,此时释放锁,t1线程重新拿到锁,才可以执行“wait()之后”,从被通知到唤醒是有一个时间的,不是通知之后立即唤醒。
- t2线程释放锁对象
- t1线程再次被CPU调度执行
当我们在多个线程中加入锁之后,由于这些线程是抢占式并发执行的,这些线程就会去竞争这把锁,当某一个线程竞争到锁之后,如果由于缺乏某些条件导致CPU没有执行该线程,然后该线程释放锁之后还会继续去参与竞争。如果极端情况下一直都是该线程抢到锁,,就像没有抢到食物被饿死了一样,这就叫做“线程饿死”现象。
使用wait()就可以让这个缺乏条件的线程进入Waiting状态(阻塞),从而避免不满足条件的该线程继续竞争,当满足条件是,在合适的时机唤醒该线程即可。
notifyAll() - 全部唤醒
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程. notifyAll()虽然是全部唤醒,但是这个过程也是一个一个(串行)线程的唤醒的,因为要唤醒线程之前要先拿到锁对象。
wait和notify要借助同一个对象
wait 和 sleep 的比较
wait()用于线程之间的通信;sleep()让线程阻塞一段时间,唯一的相同点就是都可以让线程放弃执行一段时间。
- wait 需要搭配 synchronized 使用 sleep() 不需要
- sleep是Thread类的静态方法,wait()是Object类的成员方法
- wait被notify()唤醒是通过线程间通信,不会报异常,sleep被中途唤醒会报异常