Documentation

The Java™ Tutorials
Hide TOC
Guarded Blocks保护块
Trail: Essential Java Classes
Lesson: Concurrency

Guarded Blocks保护块

Threads often have to coordinate their actions. 线程通常必须协调它们的操作。The most common coordination idiom is the guarded block. 最常见的协调习惯用法是受保护的块。Such a block begins by polling a condition that must be true before the block can proceed. 这样的块首先轮询一个条件,该条件必须为true才能继续。There are a number of steps to follow in order to do this correctly.要正确执行此操作,需要遵循许多步骤。

Suppose, for example guardedJoy is a method that must not proceed until a shared variable joy has been set by another thread. 例如,假设guardedJoy是一个方法,在另一个线程设置共享变量joy之前,该方法不能继续。Such a method could, in theory, simply loop until the condition is satisfied, but that loop is wasteful, since it executes continuously while waiting.理论上,这种方法可以简单地循环直到满足条件,但这种循环是浪费的,因为它在等待时连续执行。

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

A more efficient guard invokes Object.wait to suspend the current thread. 一个更高效的保护调用Object.wait以挂起当前线程。The invocation of wait does not return until another thread has issued a notification that some special event may have occurred — though not necessarily the event this thread is waiting for: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. 始终在测试等待的条件的循环中调用waitDon't assume that the interrupt was for the particular condition you were waiting for, or that the condition is still true. 不要假设中断是针对您正在等待的特定条件,或者该条件仍然为真。

Like many methods that suspend execution, wait can throw InterruptedException. 像许多暂停执行的方法一样,wait可以抛出InterruptedExceptionIn this example, we can just ignore that exception — we only care about the value of joy.在这个例子中,我们可以忽略这个异常—我们只关心joy的值。

Why is this version of guardedJoy synchronized? 为什么这个版本的guardedJoy是同步的?Suppose d is the object we're using to invoke wait. 假设d是我们用来调用wait的对象。When a thread invokes d.wait, it must own the intrinsic lock for d — otherwise an error is thrown. 当线程调用d.wait时,它必须拥有d的内部锁—否则将抛出一个错误。Invoking wait inside a synchronized method is a simple way to acquire the intrinsic lock.在同步方法中调用wait是获取内部锁的一种简单方法。

When wait is invoked, the thread releases the lock and suspends execution. 调用wait时,线程释放锁并暂停执行。At some future time, another thread will acquire the same lock and invoke Object.notifyAll, informing all threads waiting on that lock that something important has happened:在将来的某个时间,另一个线程将获取相同的锁并调用Object.notifyAll,通知等待该锁的所有线程发生了重要的事情:

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

Some time after the second thread has released the lock, the first thread reacquires the lock and resumes by returning from the invocation of wait.在第二个线程释放锁后的一段时间,第一个线程通过调用wait返回来重新获得锁并恢复。


Note: There is a second notification method, notify, which wakes up a single thread. 还有第二种通知方法notify,它唤醒单个线程。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. 因为notify不允许指定唤醒的线程,所以它只在大规模并行应用程序中有用;也就是说,有大量线程的程序都在做类似的杂务。In such an application, you don't care which thread gets woken up. 在这样的应用程序中,您不关心哪个线程被唤醒。

Let's use guarded blocks to create a Producer-Consumer application. 让我们使用受保护的块来创建生产者-消费者应用程序。This kind of application shares data between two threads: the producer, that creates the data, and the consumer, that does something with it. 这种应用程序在两个线程之间共享数据:创建数据的生产者和处理数据的消费者The two threads communicate using a shared object. 这两个线程使用共享对象进行通信。Coordination is essential: the consumer thread must not attempt to retrieve the data before the producer thread has delivered it, and the producer thread must not attempt to deliver new data if the consumer hasn't retrieved the old data.协调是必不可少的:使用者线程不得在生产者线程交付数据之前尝试检索数据,如果使用者未检索到旧数据,生产者线程不得尝试交付新数据。

In this example, the data is a series of text messages, which are shared through an object of type Drop:在本例中,数据是一系列文本消息,通过Drop类型的对象共享:

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

The producer thread, defined in Producer, sends a series of familiar messages. Producer中定义的producer线程发送一系列熟悉的消息。The string "DONE" indicates that all messages have been sent. 字符串“DONE”表示所有消息都已发送。To simulate the unpredictable nature of real-world applications, the producer thread pauses for random intervals between messages.为了模拟现实世界应用程序的不可预测性,生产者线程暂停消息之间的随机间隔。

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");
    }
}

The consumer thread, defined in Consumer, simply retrieves the messages and prints them out, until it retrieves the "DONE" string. Consumer中定义的consumer线程只检索消息并将其打印出来,直到检索到“DONE”字符串。This thread also pauses for random intervals.此线程也会暂停随机间隔。

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) {}
        }
    }
}

Finally, here is the main thread, defined in ProducerConsumerExample, that launches the producer and consumer threads.最后,这里是在ProducerConsumerExample中定义的主线程,它启动生产者和消费者线程。

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. 编写Drop类是为了演示受保护的块。To avoid re-inventing the wheel, examine the existing data structures in the Java Collections Framework before trying to code your own data-sharing objects. 为了避免重新发明轮子,在尝试编写自己的数据共享对象之前,请检查Java集合框架中现有的数据结构。For more information, refer to the Questions and Exercises section. 有关更多信息,请参阅问题和练习部分。

Previous page: Starvation and Livelock
Next page: Immutable Objects