wait()notify()notify All()方法

90 阅读6分钟

开启掘金成长之旅**!这是我参与「掘金日新计划 · 2 月更文挑战」的第 15天,点击查看活动详情**

1.wait()和notify()方法

由于线程之间是抢占式执行的, 因此线程之间执行的先后顺序难以预知,但是实际开发中有时候我们希望合理的协调多个线程之间的执行先后顺序

多线程引起的不安全问题往往是因为抢占式执行,随即调度,因此需要程序员来控制线程之间的执行顺序,虽然线程在内核里的调度是随机的,但是可以让线程阻塞,主动给放弃CPU,让别的线程先执行

例如,t1线程和t2线程,先让t1线程执行,t2线程先wait(阻塞),等待t1执行一部分,然后通过notify通知t2,唤醒t2,让t2执行(使用sleep和join也可以,但是wait和notify能更好地解决问题)

使用join,必须t1先执行完,t2才能执行.如果想让t1执行一般,t2就执行,join做不到!

使用sleep,是指定一个休眠时间,但是无法知道t1具体得花费多少时间不好估计,容易出现偏差

wait,notify,notifyAll这几个方法都是Object类的方法,java中的所有类都是继承于这个类的,因此任意对象都有这三个方法!

1.1 wait()方法

看一个案例

public class ThreadDemo17 {
    public static void main(String[] args) throws InterruptedException {
        Object ob = new Object();
        ob.wait();
    }
}

​编辑

这里出发了不合法的监视器(synchronized)状态异常,锁的状态就分为加锁和解锁状态,非法就是预期的是什么状态,结果是另一个状态,产生异常就要直到wait干了什么事

wait的功能

释放当前的锁

使当前执行代码的线程进行等待. (把线程放到等待队列中)

满足一定条件时被唤醒, 重新尝试获取这个锁

当前ob对象没有加锁,因此wait还无法释放锁,就会产生非法的锁状态异常

    public static void main(String[] args) throws InterruptedException {
        Object ob = new Object();
        System.out.println("wait之前");
        synchronized (ob){
            ob.wait();
        }
        System.out.println("wait之后");
    }

​编辑

我们看到wait之后线程就阻塞了,不会再执行,没有打印"wait之后"

注意:虽然这里wait之后主线程阻塞在synchronized代码块中,此时处于WAITING状态,但是这里的阻塞释放了锁,其它线程是可以获取到object这个对象的锁

wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常

wait 结束等待的条件

其他线程调用该对象的 notify 方法

wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间)

其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常

1.2 notify()方法

notify 方法是唤醒等待的线程

方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的 其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁

如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程(并没有 "先来后到")

在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行 完,也就是退出同步代码块之后才会释放对象锁

notify也要搭配 synchronized 来使用

观察一个notify唤醒线程的案例

public class ThreadDemo18 {
    public static void main(String[] args) {
        Object object = new Object();
        //t1线程用来进行wait
        Thread t1 = new Thread(()->{
            synchronized (object){
                System.out.println("wait之前");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("wait之后");
            }
        });
        Thread t2 = new Thread(()->{
            System.out.println("notify之前");
            synchronized (object){
                //notify必须获取到锁才能进行通知
                object.notify();
            }
            System.out.println("notify之后");
        });
        t1.start();
         try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}

​编辑

此处先执行了wait,然后wait操作阻塞了.没有看到紧接着wait之后的打印,接下来执行t2,进行了notify之后,才会唤醒t1的wait,t1才继续执行,打印wait之后.线程start之间添加sleep是保证大概率情况下,t1先于t2执行,否则t2先执行,notify时,没有对应的wait响应,那么t2就是进行了无效的通知,不过也没有别的影响,就是相当于白通知了!

如果t2不进行notify,那么t1就会一直阻塞,等待其它线程的唤醒,这样死等容易出问题

​编辑

因此还提供了一个带参的wait(),参数为指定的等待的最大时间,等待最大时间还没有被唤醒,就直接自动唤醒,继续执行

​编辑

​编辑

wait与sleep的区别(面试题)

和wait比较相似,sleep也是休眠指定时间,也都能被提前唤醒,sleep是通过interrupt唤醒,wait是通过notify唤醒.但是表示的含义不同,notify是正常唤醒,逻辑是正常的,sleep被提前唤醒则是出现了异常,是不正常的逻辑

wait需要搭配synchronized使用,sleep不需要

wait是Object的方法,sleep是Thread的静态方法

1.3 notifyAll()方法

notify 方法是唤醒等待的线程,使用notifyAll方法可以一次唤醒所有的等待线程

看一个案例:设置多个线程,先使用notify方法,看唤醒了几个线程,再将notify替换为notifyAll,观察唤醒了几个线程

public class WaitAndNotify {
public static void main(String[] args) {
        Object co = new Object();
        System.out.println(co);

        for (int i = 0; i < 5; i++) {
            MyThread t = new MyThread("Thread" + i, co);
            t.start();
        }

        try {
            Thread.sleep(2000);
            System.out.println("-----Main Thread notify-----");
            synchronized (co) {
                co.notify();
            }

            Thread.sleep(2000);
            System.out.println("Main Thread is end.");

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static class MyThread extends Thread {
    private String name;
        private Object co;

        public MyThread(String name, Object o) {
            this.name = name;
            this.co = o;
        }

@Override
        public void run() {
            System.out.println(name + " is waiting.");
            try {
                synchronized (co) {
                    co.wait();
                }
                System.out.println(name + " has been notified.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

​编辑

5个线程都在阻塞中,使用notify方法,只唤醒了一个等待的线程

我们将notify换成notifyAll后:

​编辑

​编辑

所有阻塞等待的线程都被唤醒

注意: 虽然是同时唤醒这些线程, 但是这些线程需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行.

1.4 多个线程使用wait和notify方法

案例:有三个线程,分别只能打印ABC,通过使用wait和notify方法控制三个线程按固定的顺序打印ABC

public class ThreadDemo19 {
    public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
        Thread t1 = new Thread(()->{
            System.out.println("A");
            synchronized (locker1){
                locker1.notify();
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (locker1){
                try {
                    locker1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("B");
            synchronized (locker2){
                locker2.notify();
            }
        });
        Thread t3 = new Thread(()->{
            synchronized (locker2){
                try {
                    locker2.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("C");
        });
        t2.start();
        t3.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t1.start();
    }
}

程序中如果先执行t2的wait,后执行t1的notify, 是没问题的,但是可能存在这种情况:如果调度顺序是先t1中的notify,那么就不会唤醒t2了.程序就僵持在这里了.解决办法就是让t1执行慢点,让其他线程先执行,这样就万无一失了

结果

​编辑