wait/notify/notifyAll 方法使用以及注意事项?

wait/notify/notifyAll 方法使用以及注意事项?

文章目录

  • wait/notify/notifyAll 方法使用以及注意事项?
    • 前言
    • 项目环境
    • 1.为什么 wait 方法必须在 synchronized 保护的同步代码中使用?
      • 1.1 官方文档
      • 1.2 示例分析
    • 2.为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?
    • 3.wait 和 sleep 方法的异同?
      • 3.1 相同点
      • 3.2 不同点
    • 4.参考

前言

本章我们讨论以下三个议题:

  • 为什么 wait 方法必须在 synchronized 保护的同步代码中使用?
  • 为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?
  • wait/notify 和 sleep 方法的异同?

项目环境

  • jdk 1.8
  • github 地址:https://github.com/huajiexiewenfeng/java-concurrent
    • 本章模块:threadbase

1.为什么 wait 方法必须在 synchronized 保护的同步代码中使用?

1.1 官方文档

java.lang.Object,java Doc 中 wait() 相关的描述

     * this method should always be used in a loop:
     * <pre>
     *     synchronized (obj) {
     *         while (&lt;condition does not hold&gt;)
     *             obj.wait();
     *         ... // Perform action appropriate to condition
     *     }
     * </pre>
     * This method should only be called by a thread that is the owner
     * of this object's monitor.

英文部分的意思是说,在使用 wait 方法时,必须把 wait 方法写在 synchronized 保护的 while 代码块中,并始终判断执行条件是否满足,如果满足就往下继续执行,如果不满足就执行 wait 方法,而在执行 wait 方法之前,必须先持有对象的 monitor 锁,也就是通常所说的 synchronized 锁。那么设计成这样有什么好处呢?

1.2 示例分析

生产者/消费者示例代码如下:

public class WaitNotifyDemo {

    static Queue<String> buffer = new LinkedList<>();

    public static void main(String[] args) throws InterruptedException {

        Object lock = new Object();

        new Thread(new Consumer(lock), "consumer-thread").start();

        for (int i = 0; i < 10; i++) {
            new Thread(new Producer("元素" + i, lock), "producer-thread").start();
        }

    }

    static class Producer implements Runnable {

        private String number;

        private Object lock;

        public Producer(String number, Object lock) {
            this.number = number;
            this.lock = lock;
        }

        @Override
        public void run() {
            synchronized (lock) {
                buffer.add(number);
                lock.notify();
                System.out.println(Thread.currentThread().getName() + "-> 添加" + number);
            }
        }
    }

    static class Consumer implements Runnable {

        private Object lock;

        public Consumer(Object lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            synchronized (lock) {
                while (buffer.isEmpty()) {
                    try {
                        System.out.println(Thread.currentThread().getName() + "-> 等待...");
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "-> 消费元素:" + buffer.poll());
            }
        }
    }
}

如果取消 synchronized 锁相关的代码,会出现什么情况呢?

  • 运行代码会抛出 IllegalMonitorStateException 异常
    wait/notify/notifyAll 方法使用以及注意事项?_第1张图片

假设程序不报异常,可能会出现如下情况,我们来分析一下,

有问题的代码出现在 51行 while (buffer.isEmpty())54行 lock.wait(); 这个过程中间

  1. 消费者线程先执行,buffer.isEmpty() 条件成立,程序继续执行,在执行 lock.wait(); 方法之前,如果线程被切换走。
  2. 比如此时生产者线程开始执行,执行 buffer.add(number);lock.notify(); 方法,但 notify 并没有任何效果,因为消费者线程的 wait 方法没来得及执行,所以没有线程在等待被唤醒。
  3. 此时再切换为消费者线程执行,继续执行 lock.wait(); 方法并进入了等待。

那么问题就出现了,我们希望的是顺序是 线程 wait()->线程 notify(),但是实际上正好相反,如果没有新的生产者线程进行生产,消费者线程将一直陷入等待中。

所以增加 synchronized 锁为了保证生产者/消费者,整个过程的原子性。

2.为什么 wait/notify/notifyAll 被定义在 Object 类中,而 sleep 定义在 Thread 类中?

主要有两点原因:

  1. Java 中每个对象都有一把称之为 monitor 监视器的锁,由于每个对象都可以上锁,这就要求在对象头中有一个用来保存锁信息的位置。这个锁是对象级别的,而非线程级别的,wait/notify/notifyAll 也都是锁级别的操作,它们的锁属于对象,所以把它们定义在 Object 类中是最合适,因为 Object 类是所有对象的父类。
  2. 如果把 wait/notify/notifyAll 方法定义在 Thread 类中,会带来很大的局限性,比如一个线程可能持有多把锁,以便实现相互配合的复杂逻辑,假设此时 wait 方法定义在 Thread 类中,如何实现让一个线程持有多把锁呢?又如何明确线程等待的是哪把锁呢?既然我们是让当前线程去等待某个对象的锁,自然应该通过操作对象来实现,而不是操作线程。

3.wait 和 sleep 方法的异同?

3.1 相同点

1.它们都可以让线程阻塞。

2.它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。

3.2 不同点

  1. wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
  2. 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。
  3. sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
  4. wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

4.参考

  • 《Java 并发编程 78 讲》- 徐隆曦

你可能感兴趣的:(Java并发编程)