并发——警戒块(Guarded Blocks)(Java tutorial 翻译)

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集合框架

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值