# 守卫的街区

> 原文： [https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html](https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html)

线程通常必须协调他们的行为。最常见的协调习语是 _ 防护块 _。这样的块开始于在块可以继续之前轮询必须为真的条件。要正确执行此操作，需要执行许多步骤。

例如，假设`guardedJoy`是在另一个线程设置共享变量`joy`之前不能继续的方法。理论上，这种方法可以简单地循环直到满足条件，但是该循环是浪费的，因为它在等待时连续执行。

```
public void guardedJoy() {
    // Simple loop guard. Wastes
    // processor time. Don't do this!
    while(!joy) {}
    System.out.println("Joy has been achieved!");
}

```

更有效的保护调用 [`Object.wait`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#wait--) 来暂停当前线程。在另一个线程发出可能发生某些特殊事件的通知之前，`wait`的调用不会返回 - 尽管不一定是该线程正在等待的事件：

```
public synchronized void guardedJoy() {
    // This guard only loops once for each special event, which may not
    // be the event we're waiting for.
    while(!joy) {
        try {
            wait();
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

```

* * *

**Note:** Always invoke `wait` inside a loop that tests for the condition being waited for. Don't assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true.

* * *

像许多暂停执行的方法一样，`wait`可以抛出`InterruptedException`。在这个例子中，我们可以忽略该异常 - 我们只关心`joy`的值。

为什么这个版本的`guardedJoy`同步？假设`d`是我们用来调用`wait`的对象。当线程调用`d.wait`时，它必须拥有`d`的内部锁 - 否则会引发错误。在同步方法中调用`wait`是获取内部锁的一种简单方法。

调用`wait`时，线程释放锁并暂停执行。在将来的某个时间，另一个线程将获取相同的锁并调用 [`Object.notifyAll`](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#notifyAll--) ，通知等待该锁定的所有线程发生了重要的事情：

```
public synchronized notifyJoy() {
    joy = true;
    notifyAll();
}

```

在第二个线程释放锁之后的一段时间，第一个线程重新获取锁并通过从`wait`的调用返回来恢复。

* * *

**Note:** There is a second notification method, `notify`, which wakes up a single thread. Because `notify` doesn't allow you to specify the thread that is woken up, it is useful only in massively parallel applications — that is, programs with a large number of threads, all doing similar chores. In such an application, you don't care which thread gets woken up.

* * *

让我们使用受保护的块来创建 _Producer-Consumer_ 应用程序。这种应用程序在两个线程之间共享数据：创建数据的 _ 生成器 _ 和用它做某事的 _ 消费者 _。两个线程使用共享对象进行通信。协调是必不可少的：消费者线程不得在生产者线程交付之前尝试检索数据，并且如果消费者未检索旧数据，则生产者线程不得尝试传递新数据。

在此示例中，数据是一系列文本消息，通过 [``Drop``](examples/Drop.java)类型的对象共享：

```
public class Drop {
    // Message sent from producer
    // to consumer.
    private String message;
    // True if consumer should wait
    // for producer to send message,
    // false if producer should wait for
    // consumer to retrieve message.
    private boolean empty = true;

    public synchronized String take() {
        // Wait until message is
        // available.
        while (empty) {
            try {
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = true;
        // Notify producer that
        // status has changed.
        notifyAll();
        return message;
    }

    public synchronized void put(String message) {
        // Wait until message has
        // been retrieved.
        while (!empty) {
            try { 
                wait();
            } catch (InterruptedException e) {}
        }
        // Toggle status.
        empty = false;
        // Store message.
        this.message = message;
        // Notify consumer that status
        // has changed.
        notifyAll();
    }
}

```

在 [``Producer``](examples/Producer.java)中定义的生产者线程发送一系列熟悉的消息。字符串“DONE”表示已发送所有消息。为了模拟真实世界应用程序的不可预测性，生产者线程暂停消息之间的随机间隔。

```
import java.util.Random;

public class Producer implements Runnable {
    private Drop drop;

    public Producer(Drop drop) {
        this.drop = drop;
    }

    public void run() {
        String importantInfo[] = {
            "Mares eat oats",
            "Does eat oats",
            "Little lambs eat ivy",
            "A kid will eat ivy too"
        };
        Random random = new Random();

        for (int i = 0;
             i < importantInfo.length;
             i++) {
            drop.put(importantInfo[i]);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
        drop.put("DONE");
    }
}

```

在 [``Consumer``](examples/Consumer.java)中定义的消费者线程只是检索消息并将其打印出来，直到它检索到“DONE”字符串。该线程也会暂停随机间隔。

```
import java.util.Random;

public class Consumer implements Runnable {
    private Drop drop;

    public Consumer(Drop drop) {
        this.drop = drop;
    }

    public void run() {
        Random random = new Random();
        for (String message = drop.take();
             ! message.equals("DONE");
             message = drop.take()) {
            System.out.format("MESSAGE RECEIVED: %s%n", message);
            try {
                Thread.sleep(random.nextInt(5000));
            } catch (InterruptedException e) {}
        }
    }
}

```

最后，这是在 [``ProducerConsumerExample``](examples/ProducerConsumerExample.java)中定义的主线程，它启动生产者和消费者线程。

```
public class ProducerConsumerExample {
    public static void main(String[] args) {
        Drop drop = new Drop();
        (new Thread(new Producer(drop))).start();
        (new Thread(new Consumer(drop))).start();
    }
}

```

* * *

**Note:** The `Drop` class was written in order to demonstrate guarded blocks. To avoid re-inventing the wheel, examine the existing data structures in the [Java Collections Framework](../../collections/index.html) before trying to code your own data-sharing objects. For more information, refer to the [Questions and Exercises](QandE/questions.html) section.

* * *