# 如何在 Java 中使用`wait()`，`notify()`和`notifyAll()`？

> 原文： [https://howtodoinjava.com/java/multi-threading/wait-notify-and-notifyall-methods/](https://howtodoinjava.com/java/multi-threading/wait-notify-and-notifyall-methods/)

[**Java 并发**](https://howtodoinjava.com/java-concurrency-tutorial/ "multithreading in java")是一个非常复杂的主题，在编写处理在任何给定时间访问一个/多个共享资源的多个线程的应用程序代码时，需要引起很多关注。 Java 5 引入了诸如[**`BlockingQueue`**](https://howtodoinjava.com/java/multi-threading/how-to-use-blockingqueue-and-threadpoolexecutor-in-java/ "How to use BlockingQueue and ThreadPoolExecutor in java")和`Executors`之类的类，它们通过提供易于使用的 API 消除了一些复杂性。

使用并发类的程序员会比使用`wait()`，`notify()`和`notifyAll()`方法调用直接处理同步内容的程序员更有信心。 我还将建议您自己通过同步使用这些较新的 API，但是由于种种原因，我们经常需要这样做，例如维护旧版代码。 掌握这些方法的丰富知识将在您到达这种情况时为您提供帮助。

在本教程中，我将讨论 Java 中`wait() notify() notifyall()`的用途。 我们将了解`wait`和`notify`之间的**区别**。

> 阅读更多： [Java 中的`wait()`和`sleep()`之间的区别](https://howtodoinjava.com/java/multi-threading/difference-between-sleep-and-wait/)

## 1\. 什么是`wait()`，`notify()`和`notifyAll()`方法？

Java 中的`Object`类具有三个最终方法，这些方法允许线程就资源的锁定状态进行通信。

1.  #### `wait()`

    它告诉调用线程放弃锁定并进入睡眠状态，直到其他线程进入同一监视器并调用`notify()`为止。 `wait()`方法在等待之前释放锁，并在从`wait()`方法返回之前重新获取锁。 实际上，`wait()`方法与同步锁紧密集成在一起，使用的特性无法直接从同步机制获得。

    换句话说，我们不可能仅在 Java 中实现`wait()`方法。 它是**本地方法**。

    调用`wait()`方法的常规语法如下：

    ```java
    synchronized( lockObject )
    { 
    	while( ! condition )
    	{ 
    		lockObject.wait();
    	}

    	//take the action here;
    }

    ```

2.  #### `notify()`

    它唤醒一个在同一对象上调用`wait()`的单个线程。 应该注意的是，调用`notify()`实际上并没有放弃对资源的锁定。 它告诉等待的线程该线程可以唤醒。 但是，直到通知者的同步块完成后才真正放弃锁定。

    因此，如果通知者在资源上调用`notify()`，但通知者仍需要在其同步块内对该资源执行 10 秒的操作，则一直在等待的线程将至少需要再等待通知者 10 秒，来释放对象上的锁定，即使调用了`notify()`。

    调用`notify()`方法的常规语法如下：

    ```java
    synchronized(lockObject) 
    {
    	//establish_the_condition;

    	lockObject.notify();

    	//any additional code if needed
    }

    ```

3.  #### `notifyAll()`

    它将唤醒在同一对象上调用`wait()`的所有线程。 尽管没有保证，但是在大多数情况下，优先级最高的线程将首先运行。 其他与上述`notify()`方法相同。

    调用`notify()`方法的一般语法如下：

    ```java
    synchronized(lockObject) 
    {
    	establish_the_condition;

    	lockObject.notifyAll();
    }

    ```

通常，使用`wait()`方法的线程会确认条件不存在（通常通过检查变量），然后调用`wait()`方法。 当另一个线程建立条件（通常通过设置相同的变量）时，它将调用`notify()`方法。 等待通知机制未指定特定条件/变量值是什么。 开发人员可以在调用`wait()`或`notify()`之前指定要检查的条件。

让我们写一个小程序来了解**应该如何使用**`wait()`，`notify()`和`notifyall()`方法来获得理想的结果。

## 2\. 如何一起使用`wait()`，`notify()`和`notifyAll()`方法

在本练习中，我们将使用`wait()`和`notify()`方法解决**生产者消费者问题**。 为了使程序简单并专注于`wait()`和`notify()`方法的使用，我们将只涉及一个生产者和一个消费者线程。

该程序的其他特性包括：

*   生产者线程每 1 秒钟产生一个新资源，并将其放入`taskQueue`中。
*   使用者线程需要 1 秒钟来处理`taskQueue`中消耗的资源。
*   `taskQueue`的最大容量为 5，即在任何给定时间，`taskQueue`中最多可以存在 5 个资源。
*   两个线程都无限运行。

#### 2.1 生产者线程

以下是根据我们的要求生产者线程的代码：

```java
class Producer implements Runnable
{
   private final List<Integer> taskQueue;
   private final int           MAX_CAPACITY;

   public Producer(List<Integer> sharedQueue, int size)
   {
      this.taskQueue = sharedQueue;
      this.MAX_CAPACITY = size;
   }

   @Override
   public void run()
   {
      int counter = 0;
      while (true)
      {
         try
         {
            produce(counter++);
         } 
		 catch (InterruptedException ex)
         {
            ex.printStackTrace();
         }
      }
   }

   private void produce(int i) throws InterruptedException
   {
      synchronized (taskQueue)
      {
         while (taskQueue.size() == MAX_CAPACITY)
         {
            System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
         }

         Thread.sleep(1000);
         taskQueue.add(i);
         System.out.println("Produced: " + i);
         taskQueue.notifyAll();
      }
   }
}

```

*   此处，`produce(counter++)`代码已在无限循环内编写，以便生产者以规则的间隔保持生产元素。
*   我们已经按照通用准则编写了`produce()`方法代码，以编写第一部分中提到的`wait()`方法。
*   `wait()`完成后，生产者在 taskQueue 中添加一个元素，并称为`notifyAll()`方法。 由于上次`wait()`方法是由使用者线程调用的（这就是生产者处于等待状态的原因），因此使用者可以获取通知。
*   收到通知后的使用者线程，如果准备按照书面逻辑使用该元素。
*   请注意，两个线程也都使用`sleep()`方法来模拟创建和使用元素时的时间延迟。

#### 2.2 使用者线程

以下是根据我们的要求使用的消费者线程的代码：

```java
class Consumer implements Runnable
{
   private final List<Integer> taskQueue;

   public Consumer(List<Integer> sharedQueue)
   {
      this.taskQueue = sharedQueue;
   }

   @Override
   public void run()
   {
      while (true)
      {
         try
         {
            consume();
         } catch (InterruptedException ex)
         {
            ex.printStackTrace();
         }
      }
   }

   private void consume() throws InterruptedException
   {
      synchronized (taskQueue)
      {
         while (taskQueue.isEmpty())
         {
            System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
            taskQueue.wait();
         }
         Thread.sleep(1000);
         int i = (Integer) taskQueue.remove(0);
         System.out.println("Consumed: " + i);
         taskQueue.notifyAll();
      }
   }
}

```

*   此处，`consume()`代码已在无限循环内编写，以便使用者在`taskQueue`中发现某些内容时便继续使用元素。
*   一旦`wait()`完成，使用者将删除 taskQueue 中的一个元素，并调用`notifyAll()`方法。 由于生产者线程调用了上次的`wait()`方法（这就是为什么生产者处于等待状态的原因），所以生产者会收到通知。
*   获取通知后的生产者线程（如果准备按照书面逻辑生产元素）。

#### 2.3 测试生产者消费者示例

现在让测试生产者和使用者线程。

```java
public class ProducerConsumerExampleWithWaitAndNotify
{
   public static void main(String[] args)
   {
      List<Integer> taskQueue = new ArrayList<Integer>();
      int MAX_CAPACITY = 5;
      Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
      Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
      tProducer.start();
      tConsumer.start();
   }
}

```

程序输出。

```java
Produced: 0
Consumed: 0
Queue is empty Consumer is waiting , size: 0
Produced: 1
Produced: 2
Consumed: 1
Consumed: 2
Queue is empty Consumer is waiting , size: 0
Produced: 3
Produced: 4
Consumed: 3
Produced: 5
Consumed: 4
Produced: 6
Consumed: 5
Consumed: 6
Queue is empty Consumer is waiting , size: 0
Produced: 7
Consumed: 7
Queue is empty Consumer is waiting , size: 0

```

我建议您将生产者线程和使用者线程花费的时间更改为不同的时间，并检查不同情况下的不同输出。

## 3\. 关于`wait()`，`notify()`和`notifyAll()`方法的面试问题

#### 3.1 调用`notify()`并且没有线程正在等待时会发生什么？

通常，如果正确使用这些方法，则在大多数情况下并非如此。 尽管如果在没有其他线程等待时调用`notify()`方法，则`notify()`只会返回而通知将丢失。

由于*等待和通知机制*不知道它正在发送通知的条件，因此，假设没有线程正在等待，通知就不会被听到。 稍后执行`wait()`方法的线程必须等待另一个通知发生。

#### 3.2 在`wait()`方法释放或重新获取锁的时间段内是否存在竞争条件？

`wait()`方法与锁定机制紧密集成。 在等待线程已经可以接收通知的状态之前，实际上不会释放对象锁。 这意味着仅当线程状态更改为能够接收通知时，才会保留锁定。 该系统可防止在此机制中发生任何竞赛情况。

同样，系统确保在将线程移出等待状态之前，对象应完全持有锁定。

#### 3.3 如果线程收到通知，是否可以保证条件设置正确？

简单地说，不。 在调用`wait()`方法之前，线程应始终在保持同步锁的同时测试条件。 从`wait()`方法返回后，线程应始终重新测试条件以确定是否应该再次等待。 这是因为另一个线程也可以测试条件并确定不需要等待 - 处理通知线程设置的有效数据。

当通知中涉及多个线程时，这是一种常见情况。 更具体地说，正在将处理数据的线程视为使用者。 它们使用其他线程产生的数据。 无法保证当消费者收到通知时，该通知尚未被其他消费者处理。

这样，当消费者醒来时，它无法假定其等待的状态仍然有效。 它可能在过去是有效的，但是在调用`notify()`方法之后以及使用者线程唤醒之前，状态可能已经更改。 等待线程必须提供检查状态的选项，并在通知已被处理的情况下返回到等待状态。 这就是为什么我们总是在循环中放置对`wait()`方法的调用的原因。

#### 3.4 当多个线程正在等待通知时会发生什么？ 调用`notify()`方法时，哪个线程实际获得通知？

这取决于许多因素。Java 规范没有定义要通知哪个线程。 在运行时中，哪个线程实际接收到通知取决于几个因素，包括 Java 虚拟机的实现以及程序执行期间的调度和计时问题。

即使在单个处理器平台上，也无法确定多个线程中的哪个接收通知。

就像`notify()`方法一样，`notifyAll()`方法不允许我们决定哪个线程获取通知：它们都被通知了。 当所有线程都收到通知时，可以设计出一种机制，让线程在它们之间选择哪个线程应该继续，哪个线程应该再次调用`wait()`方法。

#### 3.5 `notifyAll()`方法是否真的唤醒所有线程？

是的，没有。 所有等待的线程都被唤醒，但是它们仍然必须重新获取对象锁。 因此，线程不会并行运行：它们必须各自等待对象锁被释放。 因此，一次只能运行一个线程，并且只能在调用`notifyAll()`方法的线程释放其锁之后运行。

#### 3.6 如果根本只执行一个线程，为什么要唤醒所有线程？

有几个原因。 例如，可能有多个条件要等待。 由于我们无法控制哪个线程获取通知，因此通知完全有可能唤醒正在等待完全不同条件的线程。

通过唤醒所有线程，我们可以设计程序，以便线程在它们之间决定下一步应执行哪个线程。 另一种选择是生产者生成的数据可以满足多个消费者的需求。 由于可能难以确定有多少消费者可以对该通知感到满意，因此可以选择全部通知他们，从而允许消费者在他们之间进行分类。

学习愉快！