Guarded Blocks
线程通常必须协调其动作。最常见的协调习惯是防护区。这样的块首先轮询一个条件,该条件必须为真,然后才能 continue 进行。要正确执行此操作,需要执行许多步骤。
例如,假设guardedJoy
是在另一个线程设置了共享变量joy
之前不能 continue 进行的方法。从理论上讲,这种方法可以简单地循环直到满足条件为止,但是这种循环是浪费的,因为它在 await 时连续执行。
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
的调用不会返回-尽管不一定是该线程正在 await 的事件:
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:
始终在循环中调用wait
,以测试是否正在 await 条件。不要以为该中断是针对您正在 await 的特定条件,还是该条件仍然为真。
就像许多暂停执行的方法一样,wait
可以抛出InterruptedException
。在此示例中,我们可以忽略该异常-我们只关心joy
的值。
为什么此版本的guardedJoy
是同步的?假设d
是我们用来调用wait
的对象。当线程调用d.wait
时,它必须拥有d
的固有锁-否则将引发错误。在同步方法中调用wait
是获取内部锁的简单方法。
调用wait
时,线程释放锁并中止执行。在将来的某个时间,另一个线程将获取相同的锁并调用Object.notifyAll,通知所有在该锁上 await 的线程发生了重要的事情:
public synchronized notifyJoy() {
joy = true;
notifyAll();
}
在第二个线程释放锁后的某个时间,第一个线程重新获取该锁,并通过从wait
的调用返回来恢复。
Note:
还有第二种通知方法notify
,它唤醒一个线程。由于notify
不允许您指定被唤醒的线程,因此它仅在大规模并行应用程序中有用,即,具有大量线程且都执行类似杂务的程序。在这样的应用程序中,您不必担心哪个线程被唤醒。
让我们使用受保护的块来创建 Producer-Consumer 应用程序。这种应用程序在两个线程之间共享数据:生产者(创建数据)和 Consumer(使用数据)。这两个线程使用共享对象进行通信。协调是必不可少的:使用者线程不得在生产者线程传递数据之前try检索数据,并且如果使用者没有检索到旧数据,则生产者线程不得try传递新数据。
在此示例中,数据是一系列文本消息,它们通过类型为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();
}
}
Note:
编写Drop
类是为了演示受保护的块。为了避免重新发明轮子,请在try编写自己的数据共享对象之前,先检查Java Collections 框架中的现有数据结构。有关更多信息,请参见问题与练习部分。