被守护的代码块

线程通常需要协调它们的动作。最常见的协调方法是被守护的代码块。这样的代码块以在代码块执行之前,对一个总是为真的条件进行轮询。为了能正确完成它,需要遵循一系列的步骤。

假设,guardJoy是一个方法——它不能推进,直到一个共享变量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不允许你指定具体唤醒哪个线程,它只有在大型并发应用中才有用——在这种情况下,程序会有大量的线程,都在做相同的事情。在这种情况下,你不关心具体唤醒的是哪个线程。

让我们使用守护线程块来创建一个生产者-消费者应用。这种应用会在两个线程之间共享数据——生产者,负责创造数据,和消费者,负责对数据做一些处理。这两个线程通过共享的对象来交流。协作是非常重要的:消费者线程必须要在生产者交付它之后才能取数据,而生产者必须要等到消费者把旧数据取走,才能生产新的数据。

在这个例子中,数据是一系列的文本消息,通过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中定义,它会启动生产者和消费者的线程。

注意:Drop类是为了说明守护代码块。为了避免重复发明轮子,在发明自己的数据共享类前,先研究下JAVA集合框架中已经有的数据结构。在问题和回答章节会有更多的涉及。

你可能感兴趣的:(被守护的代码块)