5 警戒块(Guarded Blocks)
线程间必须协调它们的动作。最普通的协调方式是警戒块(Guarded Blocks)。这样的块就是,首先不断查询一个条件,直到这个条件变成“真”,才执行这个语句块。为了要正确的执行它们,我们要遵循一些步骤。
假设,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 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!"); }
|
注意:总是在一个测试等待条件的循环体内调用wait。不要假设中断就是我们要等待的特定条件,或者条件一直保持为“真”
象其它挂起执行的方法一样,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) {} } } }
|
最后是main方法,定义为ProducerConsumerExample类,它启动produce和comsumer线程。
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(); } }
|
注意:Drop类是用来演示警戒块的,为了避免重发明轮子,在编写自己的共享对象时,请查看一下Java集合框架