wait and notify() Methods in Java – Java中的wait和notify()方法

最后修改: 2018年 2月 9日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll look at one of the most fundamental mechanisms in Java — thread synchronization.

在本教程中,我们将看看Java中最基本的机制之一–线程同步。

We’ll first discuss some essential concurrency-related terms and methodologies.

我们首先讨论一些与并发症有关的基本术语和方法。

And we’ll develop a simple application where we’ll deal with concurrency issues, with the goal of better understanding wait() and notify().

我们将开发一个简单的应用程序,在那里我们将处理并发问题,目的是更好地理解wait() notify()

2. Thread Synchronization in Java

2.Java中的线程同步

In a multithreaded environment, multiple threads might try to modify the same resource. Not managing threads properly will of course lead to consistency issues.

在多线程环境中,多个线程可能试图修改同一个资源。不正确地管理线程当然会导致一致性问题。

2.1. Guarded Blocks in Java

2.1.Java中的受保护块

One tool we can use to coordinate actions of multiple threads in Java is guarded blocks. Such blocks keep a check for a particular condition before resuming the execution.

我们可以用一个工具来协调Java中多个线程的行动,那就是守护块。这种区块在恢复执行之前会对一个特定的条件进行检查。

With that in mind, we’ll make use of the following:

考虑到这一点,我们将利用以下的方法。

We can better understand this from the following diagram depicting the life cycle of a Thread:

我们可以从以下描述线程的生命周期的图中更好地理解这一点。

Java - Wait and Notify

Please note that there are many ways of controlling this life cycle. However, in this article, we’re going to focus only on wait() and notify().

请注意,有很多方法可以控制这个生命周期。然而,在这篇文章中,我们将只关注wait() notify()

3. The wait() Method

3.wait()方法

Simply put, calling wait() forces the current thread to wait until some other thread invokes notify() or notifyAll() on the same object.

简单地说,调用wait()迫使当前线程等待,直到其他线程对同一对象调用notify()notifyAll()

For this, the current thread must own the object’s monitor. According to Javadocs, this can happen in the following ways:

为此,当前线程必须拥有该对象的monitor。根据Javadocs,这可以通过以下方式发生。

  • when we’ve executed synchronized instance method for the given object
  • when we’ve executed the body of a synchronized block on the given object
  • by executing synchronized static methods for objects of type Class

Note that only one active thread can own an object’s monitor at a time.

注意,在同一时间内,只有一个活动线程可以拥有一个对象的监视器。

This wait() method comes with three overloaded signatures. Let’s have a look at these.

这个wait() 方法有三个重载签名。让我们看一下这些签名。

3.1. wait()

3.1.wait()

The wait() method causes the current thread to wait indefinitely until another thread either invokes notify() for this object or notifyAll().

wait()方法使当前线程无限期地等待,直到另一个线程为这个对象调用notify()notifyAll()

3.2. wait(long timeout)

3.2.等待(长超时)

Using this method, we can specify a timeout after which a thread will be woken up automatically. A thread can be woken up before reaching the timeout using notify() or notifyAll().

使用这个方法,我们可以指定一个超时,超时后线程将被自动唤醒。在达到超时之前,可以使用notify()notifyAll()来唤醒一个线程。

Note that calling wait(0) is the same as calling wait().

注意,调用wait(0)与调用wait()相同。

3.3. wait(long timeout, int nanos)

3.3.wait(long timeout, int nanos)

This is yet another signature providing the same functionality. The only difference here is that we can provide higher precision.

这是又一个提供相同功能的签名。这里唯一的区别是,我们可以提供更高的精度。

The total timeout period (in nanoseconds) is calculated as 1_000_000*timeout + nanos.

总的超时时间(以纳秒为单位)计算为1_000_000*timeout + nanos

4. notify() and notifyAll()

4、notify()notifyAll()

We use the notify() method for waking up threads that are waiting for access to this object’s monitor.

我们使用notify()方法来唤醒那些正在等待访问这个对象的监视器的线程。

There are two ways of notifying waiting threads.

有两种通知等待线程的方式。

4.1. notify()

4.1.notify()

For all threads waiting on this object’s monitor (by using any one of the wait() methods), the method notify() notifies any one of them to wake up arbitrarily. The choice of exactly which thread to wake is nondeterministic and depends upon the implementation.

对于所有在这个对象的监视器上等待的线程(通过使用任何一个wait()方法),方法notify()通知它们中的任何一个任意地唤醒。究竟选择哪个线程来唤醒是不确定的,取决于实现。

Since notify() wakes up a single random thread, we can use it to implement mutually exclusive locking where threads are doing similar tasks. But in most cases, it would be more viable to implement notifyAll().

由于notify()唤醒了一个随机的线程,我们可以用它来实现线程做类似任务时的互斥锁。但在大多数情况下,实现notifyAll()会更可行。

4.2. notifyAll()

4.2.notifyAll()

This method simply wakes all threads that are waiting on this object’s monitor.

这个方法只是唤醒所有在这个对象的监视器上等待的线程。

The awakened threads will compete in the usual manner, like any other thread that is trying to synchronize on this object.

被唤醒的线程将以通常的方式进行竞争,就像任何其他试图在这个对象上进行同步的线程一样。

But before we allow their execution to continue, always define a quick check for the condition required to proceed with the thread. This is because there may be some situations where the thread got woken up without receiving a notification (this scenario is discussed later in an example).

但在我们允许他们继续执行之前,一定要定义一个快速检查,以确定继续执行线程所需的条件。这是因为可能会有一些情况,线程在没有收到通知的情况下被唤醒(这种情况将在后面的例子中讨论)。

5. Sender-Receiver Synchronization Problem

5.发送者-接收者同步问题

Now that we understand the basics, let’s go through a simple SenderReceiver application that will make use of the wait() and notify() methods to set up synchronization between them:

现在我们了解了基础知识,让我们来看看一个简单的SenderReceiver应用程序,它将利用wait()notify()方法来设置它们之间的同步。

  • The Sender is supposed to send a data packet to the Receiver.
  • The Receiver cannot process the data packet until the Sender finishes sending it.
  • Similarly, the Sender shouldn’t attempt to send another packet unless the Receiver has already processed the previous packet.

Let’s first create a Data class that consists of the data packet that will be sent from Sender to Receiver. We’ll use wait() and notifyAll() to set up synchronization between them:

让我们首先创建一个Data类,它由将从Sender发送到Receiver的数据packet组成。我们将使用wait()notifyAll()来设置它们之间的同步。

public class Data {
    private String packet;
    
    // True if receiver should wait
    // False if sender should wait
    private boolean transfer = true;
 
    public synchronized String receive() {
        while (transfer) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
                System.err.println("Thread Interrupted");
            }
        }
        transfer = true;
        
        String returnPacket = packet;
        notifyAll();
        return returnPacket;
    }
 
    public synchronized void send(String packet) {
        while (!transfer) {
            try { 
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
                System.err.println("Thread Interrupted");
            }
        }
        transfer = false;
        
        this.packet = packet;
        notifyAll();
    }
}

Let’s break down what’s going on here:

让我们来分析一下这里发生了什么。

  • The packet variable denotes the data that is being transferred over the network.
  • We have a boolean variable transfer, which the Sender and Receiver will use for synchronization:
    • If this variable is true, the Receiver should wait for Sender to send the message.
    • If it’s false, Sender should wait for Receiver to receive the message.
  • The Sender uses the send() method to send data to the Receiver:
    • If transfer is false, we’ll wait by calling wait() on this thread.
    • But when it is true, we toggle the status, set our message, and call notifyAll() to wake up other threads to specify that a significant event has occurred and they can check if they can continue execution.
  • Similarly, the Receiver will use the receive() method:
    • If the transfer was set to false by Sender, only then will it proceed, otherwise we’ll call wait() on this thread.
    • When the condition is met, we toggle the status, notify all waiting threads to wake up, and return the data packet that was received.

5.1. Why Enclose wait() in a while Loop?

5.1.为什么要将wait()包含在while循环中?

Since notify() and notifyAll() randomly wake up threads that are waiting on this object’s monitor, it’s not always important that the condition is met. Sometimes the thread is woken up, but the condition isn’t actually satisfied yet.

由于notify() notifyAll() 会随机唤醒在这个对象的监视器上等待的线程,所以条件是否满足并不总是那么重要。有时线程被唤醒了,但条件实际上还没有满足。

We can also define a check to save us from spurious wakeups — where a thread can wake up from waiting without ever having received a notification.

我们还可以定义一个检查,使我们免受虚假唤醒的影响–在这种情况下,一个线程可以在没有收到通知的情况下从等待中唤醒。

5.2. Why Do We Need to Synchronize send() and receive() Methods?

5.2.为什么我们需要同步send()receive()方法?

We placed these methods inside synchronized methods to provide intrinsic locks. If a thread calling wait() method does not own the inherent lock, an error will be thrown.

我们把这些方法放在同步的方法里面,以提供内在的锁。如果调用wait()方法的线程不拥有固有锁,将抛出一个错误。

We’ll now create Sender and Receiver and implement the Runnable interface on both so that their instances can be executed by a thread.

现在我们将创建SenderReceiver,并在两者上实现Runnable接口,以便它们的实例可以被线程执行。

First, we’ll see how Sender will work:

首先,我们来看看Sender将如何工作。

public class Sender implements Runnable {
    private Data data;
 
    // standard constructors
 
    public void run() {
        String packets[] = {
          "First packet",
          "Second packet",
          "Third packet",
          "Fourth packet",
          "End"
        };
 
        for (String packet : packets) {
            data.send(packet);

            // Thread.sleep() to mimic heavy server-side processing
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
                System.err.println("Thread Interrupted"); 
            }
        }
    }
}

Let’s take a closer look at this Sender:

让我们仔细看看这个Sender

  • We’re creating some random data packets that will be sent across the network in packets[] array.
  • For each packet, we’re merely calling send().
  • Then we’re calling Thread.sleep() with random interval to mimic heavy server-side processing.

Finally, let’s implement our Receiver:

最后,让我们实现我们的Receiver

public class Receiver implements Runnable {
    private Data load;
 
    // standard constructors
 
    public void run() {
        for(String receivedMessage = load.receive();
          !"End".equals(receivedMessage);
          receivedMessage = load.receive()) {
            
            System.out.println(receivedMessage);

            //Thread.sleep() to mimic heavy server-side processing
            try {
                Thread.sleep(ThreadLocalRandom.current().nextInt(1000, 5000));
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); 
                System.err.println("Thread Interrupted"); 
            }
        }
    }
}

Here, we’re simply calling load.receive() in the loop until we get the last “End” data packet.

在这里,我们只是在循环中调用load.receive(),直到我们得到最后一个“End”数据包。

Let’s now see this application in action:

现在让我们看看这个应用程序的运作情况。

public static void main(String[] args) {
    Data data = new Data();
    Thread sender = new Thread(new Sender(data));
    Thread receiver = new Thread(new Receiver(data));
    
    sender.start();
    receiver.start();
}

We’ll receive the following output:

我们将收到以下输出。

First packet
Second packet
Third packet
Fourth packet

And here we are. We’ve received all data packets in the right, sequential order and successfully established the correct communication between our sender and receiver.

我们就在这里。我们已经按照正确的顺序收到了所有的数据包,并成功地在我们的发送方和接收方之间建立了正确的通信。

6. Conclusion

6.结论

In this article, we discussed some core synchronization concepts in Java. More specifically, we focused on how we can use wait() and notify() to solve interesting synchronization problems. Finally, we went through a code sample where we applied these concepts in practice.

在这篇文章中,我们讨论了Java中的一些核心同步概念。更具体地说,我们重点讨论了如何使用wait()notify()来解决有趣的同步问题。最后,我们通过一个代码示例,将这些概念应用于实践中。

Before we close, it’s worth mentioning that all these low-level APIs, such as wait(), notify() and notifyAll(), are traditional methods that work well, but higher-level mechanisms are often simpler and better — such as Java’s native Lock and Condition interfaces (available in java.util.concurrent.locks package).

在结束之前,值得一提的是,所有这些低级别的API,例如wait()notify()notifyAll(),都是工作良好的传统方法,但更高级别的机制往往更简单、更好–例如Java的本地LockCondition接口(可在java.util.concurrent.lock包中获取)。

For more information on the java.util.concurrent package, visit our overview of the java.util.concurrent article. And Lock and Condition are covered in the guide to java.util.concurrent.Locks.

有关java.util.concurrent包的更多信息,请访问我们的java.util.concurrent的概述文章。而LockConditionjava.util.concurrent.Locks指南中有所介绍。

As always, the complete code snippets used in this article are available over on GitHub.

一如既往,本文中使用的完整代码片段可在GitHub上获得