线程经常需要协调它们的活动,最常用的协调方法是保护块。这样的块是以轮询一个条件开始,这个条件值必须是true,在块处理前。为了正确做到这样,有大量的步骤需要遵守。
例如,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来暂停当前线程。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) {}
}
注意:总是调用循环中的wait来测试正在等待的条件。不要假设中断(interrupt)是你正在等待的条件或者那个条件是true。
像许多暂停执行的方法,wait会抛出InterruptedException。在这个例子中,我们能忽略这个异常,仅仅关注joy的值。
为什么这个版本的guardedJoy是同步的?假设d是我们用来调用wait的对象。当一个线程调用d.wait,它必须拥有d的内在锁---否则将抛出一个错误。在一个同步方法中调用wait是一种简单的方式获取内在锁。
当wait被调用,那个线程就释放锁并暂停执行。在不远的将来,另外一个线程将获取同样的锁并调用object.notifyAll,通知在这个锁上等待的所有线程,一些重要的事情发生了。
public synchronized notifyJoy() {
joy = true;
notifyAll();
}
在第二个线程释放了锁后的一段时间,第一个线程重新获取锁并从wait的调用通过返回继续执行。
注意:有第二个通知方法,notify,唤醒一个单一线程。因为notify不允许你指定要唤醒的线程,只有在大规模并行的应用程序中有用---即,程序中有大量的线程,所有的做那相似的工作。在这样的应用程序中,你不必在意哪个线程被唤醒。
让我们使用保护块创建一个生产者--消费者应用程序。这类的应用在两个线程之间共享数据:生产者创建数据,消费者使用数据做一些事情。两个线程使用一个共享对象通信。协调就是必须的:在生产者传递数据前,消费者不必试着去获取它,并且生产者也不必试着去传递新的数据,如果消费者还没有获取旧的数据。
在这个例子中,数据是一系列的文本信息,通过一个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();
}
}
生产者线程,用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");
}
}
消费者线程,以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) {}
}
}
}
最后,这是主线程,以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();
}
}
注意: 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.