Java并发编程(五)保护块

    xiaoxiao2025-11-06  2

    5. 保护块

    线程通常需要协调任务。最常用的协调方法是保护块(guarded block)。这样一个块以一个条件开始,如果该条件为真,则该块的代码得以执行。这样做需要遵循一些步骤。

    假设,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) {} } 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不允许你指定被唤醒的线程,它仅仅在大规模并行程序中使用,也就是,包含大量做相同任务的线程的程序。在这样的程序中,你不用担心哪个线程被唤醒。

    让我们用保护块来实现生产者消费者(Producer-Consumer)程序。这个 程序在两个线程中共享数据,生产者,产生数据,消费者,使用数据。这两个线程通过共享对象通信。协调很关键:消费者不会在生产者产生数据之前取得数据,并且,开发者不会在消费者取得旧数据之前产生新的数据。

    在接下来的例子中,数据是一系列文本消息,通过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(); } }

    注意:Drop为了模拟保护块。为了避免重新造轮子,请在创建共享数据的结构时,检查Java Collections Framework中已经存在的数据结构。更多的信息,请参考Questions and Exercises: Concurrency。

    文章翻译自Java Tutorials,Guarded Blocks,翻译难免会有纰漏,欢迎读者讨论指正。

    转载请注明原文地址: https://ju.6miu.com/read-1303891.html
    最新回复(0)