Guarded Blocks <译>

November 17, 2022 作者: yijianhao 分类: jdk文档翻译 浏览: 145 评论: 0

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. There are a number of steps to follow in order to do this correctly.

线程通常必须协调他们的操作,guarded block最常见的协调习惯,这种区块首先会轮询一个条件,只有条件为true的时候,区块才能继续执行。为了正确执行此操作,需要遵循许多步骤。

Suppose, for example guardedJoy is a method that must not proceed until a shared variable joy has been set by another thread. Such a method could, in theory, simply loop until the condition is satisfied, but that loop is wasteful, since it executes continuously while waiting.

假如guardedJoy是一个方法,它在共享变量joy被其他线程设置完之前都不会执行。理论上讲,这种方法可以进行简单的循环等待直到条件被满足,但是这种循环是很浪费资源的,因为他在等待的时候仍会继续执行。

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. 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:

一个更加高效的守护方式是调用Object.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。不要假设发生了中断就是针对你正在等待的特定条件,或这个特定条件依旧为true

Like many methods that suspend execution, wait can throw InterruptedException. In this example, we can just ignore that exception — we only care about the value of joy.

与许多暂停执行的方法一样,等待可能会引发中断异常(InterruptedException)在这个例子中,我们可以忽略这个异常——仅关心joy的值。

Why is this version of guardedJoy synchronized? Suppose d is the object we’re using to invoke wait. When a thread invokes d.wait, it must own the intrinsic lock for d — otherwise an error is thrown. Invoking wait inside a synchronized method is a simple way to acquire the intrinsic lock.

为什么这个版本的guardedJoy方法是同步的?假设d是我们用来调用wait的对象,当一个线程调用了d.wait,它必须获得d的内部锁——否则会抛出一个异常。在同步方法(synchronized method)中调用wait是一个获得内部锁的简单方式。

When wait is invoked, the thread releases the lock and suspends execution. 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:

一旦wait被访问了,线程就会释放锁并且暂停执行。在之后的某个时刻,其他线程将会获得相同的锁并且调用Object.notifyAll提醒等待着这个锁的所有线程,发生了重要的事情:(译者:大概意思是说,joy的值改成true了,赶紧告诉其他线程)

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. 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.
注意: 还有第二种通知方法:notify,它唤醒单个线程,由于notify不允许你唤醒指定的线程,因此它仅在大规模并行应用中有用——即具有大量线程且都执行类似任务的程序。在这样的应用中,你不必关系哪个线程被唤醒。
(关于notify和notifyAll的区别,知乎上有个通俗的比喻,可以看一下:
https://www.zhihu.com/question/37601861/answer/308613735 )

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.

让我们来使用guarded blocks来创建一个“生产者——消费者”应用。这个类型的应用在两个线程之间共享数据:生产者创建数据,消费者使用数据做了些什么。两个线程使用共享对象进行通信。协调至关重要:消费者不能在生产者传递数据之前检索数据;并且在消费者尚未检索旧数据之前,生产者线程不能尝试传递新数据。

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. The string “DONE” indicates that all messages have been sent. To simulate the unpredictable nature of real-world applications, the producer thread pauses for random intervals between messages.

生产者线程(定义在Producer类中)发送一系列相似的消息。字符串“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");
    }
}

The consumer thread, defined in Consumer, simply retrieves the messages and prints them out, until it retrieves the “DONE” string. This thread also pauses for random intervals,

消费者线程(定义在Consumer类中),简单地检索消息并且打印它出来,直到检索出到“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) {}
        }
    }
}

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. 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. For more information, refer to the Questions and Exercises section.
注意:Drop类为了演示guarded blocks而写的,避免重复造轮子,在你尝试编写你自己的数据共享类之前,请检索Java Collections框架中已经存在的数据结构。更多的信息,请参阅Questions和Exercises部分。


评论