由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知.
但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序.
完成这个协调工作, 主要涉及到三个方法:
(join 也是控制线程执行顺序的一种方式,但是 join 更倾向于控制线程结束)
注意: wait, notify, notifyAll 都是 Object 类的方法.
wait 做的事情:
wait 要搭配 synchronized 来使用,因为你必须先持有锁才能释放锁,脱离 synchronized 使用 wait 会直接抛出异常.
wait 结束等待的条件:
代码示例: 观察wait()方法使用:
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
synchronized (object) {
System.out.println("等待中");
object.wait();
System.out.println("等待结束");
}
}
这样在执行到object.wait()之后就一直等待下去,那么程序肯定不能一直这么等待下去了。这个时候就需要使用到了另外一个方法唤醒的方法notify()。
sleep 方法和 wait 方法都是用来将线程进入休眠状态的,并且 sleep 和 wait 方法都可以响应 interrupt 中断,也就是线程在休眠的过程中,如果收到中断信号,都可以进行响应并中断,且都可以抛出 InterruptedException 异常。
区别:
notify 方法是唤醒等待的线程.
代码示例: 使用notify()方法唤醒线程
class Singleton {
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
// 加锁
synchronized (locker) {
while (true) {
try {
System.out.println("wait 开始");
// 释放并等待锁
locker.wait();
System.out.println("wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
// 加锁
synchronized (locker) {
System.out.println("notify 开始");
// 释放锁,唤醒等待的线程.
locker.notify();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
Thread.sleep(1000);
t2.start();
}
}
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
代码示例:
class Singleton {
static class WaitTask implements Runnable {
private Object locker;
public WaitTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
while (true) {
try {
System.out.println(Thread.currentThread().getName() + " :wait 开始");
locker.wait();
System.out.println(Thread.currentThread().getName() + " :wait 结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
static class NotifyTask implements Runnable {
private Object locker;
public NotifyTask(Object locker) {
this.locker = locker;
}
@Override
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notify();
System.out.println("notify 结束");
}
}
}
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
Thread t1 = new Thread(new WaitTask(locker));
Thread t3 = new Thread(new WaitTask(locker));
Thread t4 = new Thread(new WaitTask(locker));
Thread t2 = new Thread(new NotifyTask(locker));
t1.start();
t3.start();
t4.start();
Thread.sleep(1000);
t2.start();
}
}

从结果可以看出 notify 只能唤醒一个线程。
public void run() {
synchronized (locker) {
System.out.println("notify 开始");
locker.notifyAll();
System.out.println("notify 结束");
}
}
多次执行的结果:

此时可以看到, 调用 notifyAll 能同时唤醒 3 个wait 中的线程。
注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行,但是这个顺序是不固定的,哪个线程先抢到锁,哪个就先执行.
notify方法只是唤醒某一个等待线程. 使用notifyAll方法可以一次唤醒所有的等待线程.
通过上面两段代码可能会有一些疑惑:
为什么 notify 唤醒一个线程,这个线程被唤醒,当这个线程用完再次释放锁时,其他线程不会获得锁 ,为什么 ?
而使用 notifyAll 唤醒所有的线程后,一个线程先获得锁,当他用完释放锁之后,另外几个线程能够获得锁,这是为什么 ?
这里面先介绍两个概念 :
看到这,大家应该就知道答案了:
多个线程都 wait, 被放入等待池,
调用 notify 时,其中一个线程被唤醒放到锁池中,然后参与锁的竞争,抢到锁以后继续执行代码,当释放锁之后因为其他线程还在等待池中,所以不会竞争锁。
但是调用 notifyAll 时,所有的线程都被放入锁池,全部参与锁竞争,当一个线程用完锁释放后,其他线程会继续竞争并获得锁。
好啦! 以上就是对 wait 、notify 和 notifyAll 的讲解,希望能帮到你 !
评论区欢迎指正 !