Java并发编程(五)保护块

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,翻译难免会有纰漏,欢迎读者讨论指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值