Guarded Blocks
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)。以循环条件开始的代码块必须在循环条件为真时代码块才能够执行。为了正确操作有许多步骤需要遵守。
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:
一个更有效的保护(guard)是调用Object.wait去暂停当前线程。wait的调用不会返回直到其他的线程发出某个特别的事件已经发生的通知-虽然不见得是这个线程正在等待的事件。(有可能是发生了异常)
public synchronized 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方法。不要假设中断(interrupt)是你要等待的特定条件,因为条件仍然还是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.
像许多暂停执行的方法,wait能够抛出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.
为什么这个版本的guardJoy要同步?假设d是我们用来调用wait的对象。当一个线程调用d.wait,它肯定拥有d的内置锁-否则会抛出一个错误。在同步方法里调用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,这会通知所有在等待lock的线程有一些重要的事情发生了
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不允许你指定要唤醒的线程,这个在大规模并行程序中有用处-即,程序中有大量的线程,并且都在做类似的事情,在这样的程序中,你不必在意哪个线程被唤醒了。
--------------------------------------------------------------------------------
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)来创建一个生产者-消费者应用程序。这类程序在线程间共享数据:生产者,负责生产数据,消费者,负责使用数据。这2个线程使用共享对象通信。协调是必不可少的:消费者不能够在生产者递送数据前检索它,并且如果消费者还没有取得老数据生产者不能够递送新数据
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.
最后,这是main线程,定义在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.