【多线程】wait 、notify 和 notifyAll 讲解

wait 、notify 和 notifyAll 讲解

  • 一. wait
  • 二. wait 和 sleep 的对比
  • 三. notify
  • 四. notifyAll
  • 五. notify 与 notifyAll 的原理

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

完成这个协调工作, 主要涉及到三个方法:

  • wait() / wait(long timeout): 让当前线程进入等待状态.
  • notify() / notifyAll(): 唤醒在当前对象上等待的线程.

(join 也是控制线程执行顺序的一种方式,但是 join 更倾向于控制线程结束)
注意: wait, notify, notifyAll 都是 Object 类的方法.

一. wait

wait 做的事情:

  • 使当前执行代码的线程进行等待. (把线程放到等待(阻塞)队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 要搭配 synchronized 来使用,因为你必须先持有锁才能释放锁,脱离 synchronized 使用 wait 会直接抛出异常.

wait 结束等待的条件:

  • 其他线程调用该对象的 notify 方法.
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

代码示例: 观察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()。

二. wait 和 sleep 的对比

sleep 方法和 wait 方法都是用来将线程进入休眠状态的,并且 sleep 和 wait 方法都可以响应 interrupt 中断,也就是线程在休眠的过程中,如果收到中断信号,都可以进行响应并中断,且都可以抛出 InterruptedException 异常。

区别:

  1. wait 是 Object 的方法, sleep 是 Thread 的静态方法。
  2. wait 需要搭配 synchronized 使用,sleep 不需要。
  3. wait 方法会主动的释放锁,而 sleep 方法则不会。
  4. sleep 过了超时时间之后,线程会自动唤醒, 而不传递任何参数的 wait 方法只能被动的被唤醒。
  5. 调用 sleep 方法线程会进入 TIMED_WAITING 有时限等待状态,而调用无参数的 wait 方法,线程会进入 WAITING 无时限等待状态。

三. notify

notify 方法是唤醒等待的线程.

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。

代码示例: 使用notify()方法唤醒线程

  • 创建 WaitTask 类, 对应一个线程, run 内部循环调用 wait.
  • 创建 NotifyTask 类, 对应另一个线程, 在 run 内部调用一次 notify
  • 注意, WaitTask 和 NotifyTask 内部持有同一个 Object locker. WaitTask 和 NotifyTask 要想配合就需要搭配同一个 Object.
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();
    }
}

四. notifyAll

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

代码示例:

  • 使用 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(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();
    }
}

【多线程】wait 、notify 和 notifyAll 讲解_第1张图片

从结果可以看出 notify 只能唤醒一个线程。

  • 修改 NotifyTask 中的 run 方法, 把 notify 替换成 notifyAll
public void run() {
    synchronized (locker) {
        System.out.println("notify 开始");
        locker.notifyAll();
        System.out.println("notify 结束");
   }
}

多次执行的结果:
【多线程】wait 、notify 和 notifyAll 讲解_第2张图片
此时可以看到, 调用 notifyAll 能同时唤醒 3 个wait 中的线程。

注意: 虽然是同时唤醒 3 个线程, 但是这 3 个线程需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行,但是这个顺序是不固定的,哪个线程先抢到锁,哪个就先执行.

五. notify 与 notifyAll 的原理

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

通过上面两段代码可能会有一些疑惑:
为什么 notify 唤醒一个线程,这个线程被唤醒,当这个线程用完再次释放锁时,其他线程不会获得锁 ,为什么 ?
而使用 notifyAll 唤醒所有的线程后,一个线程先获得锁,当他用完释放锁之后,另外几个线程能够获得锁,这是为什么 ?

这里面先介绍两个概念 :

  • 等待池: 假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池,等待池中的线程不会去竞争该对象的锁。
  • 锁池: 锁池中的线程是可以竞争锁的,当一个线程被唤醒(notify、notifyAll )时会被放到该对象的锁池,当锁被释放时(有线程 wait )时,锁池中的线程会竞争锁,对象的锁每次只有一个线程可以获得,其他线程只能在锁池中等待,等到锁被释放时继续竞争。

看到这,大家应该就知道答案了:

多个线程都 wait, 被放入等待池,

  • 调用 notify 时,其中一个线程被唤醒放到锁池中,然后参与锁的竞争,抢到锁以后继续执行代码,当释放锁之后因为其他线程还在等待池中,所以不会竞争锁。

  • 但是调用 notifyAll 时,所有的线程都被放入锁池,全部参与锁竞争,当一个线程用完锁释放后,其他线程会继续竞争并获得锁。

好啦! 以上就是对 wait 、notify 和 notifyAll 的讲解,希望能帮到你 !
评论区欢迎指正 !

你可能感兴趣的:(多线程,java,开发语言,多线程)