След: Существенные Классы
Урок: Параллелизм
Защищенные Блоки
Домашняя страница > Существенные Классы > Параллелизм

Защищенные Блоки

Потоки часто должны координировать свои действия. Наиболее распространенная идиома координации является защищенным блоком. Такой блок начинается, опрашивая условие, которое должно быть истиной прежде, чем блок сможет продолжиться. Есть много шагов, чтобы следовать, чтобы сделать это правильно.

Предположите, например 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, отправляет ряд знакомых сообщений. "СДЕЛАННАЯ" строка указывает, что все сообщения были отправлены. Моделировать непредсказуемый характер реальных приложений, пауз потока производителя для случайных интервалов между сообщениями.


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, просто получает сообщения и распечатывает их, пока это не получает "СДЕЛАННУЮ" строку. Этот поток также паузы для случайных интервалов.


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 class был записан, чтобы демонстрировать защищенные блоки. Чтобы избежать повторно изобретать колесо, исследуйте существующие структуры данных в Платформе Наборов Java прежде, чем попытаться кодировать Ваши собственные объекты совместного использования данных. Для получения дополнительной информации обратитесь к разделу Вопросов и Упражнений.

Проблемы с примерами? Попытайтесь Компилировать и Выполнить Примеры: FAQ.
Жалобы? Поздравление? Предложения? Дайте нам свою обратную связь.

Предыдущая страница: Исчерпание ресурсов и Динамическая взаимоблокировка
Следующая страница: Неизменные Объекты



Spec-Zone.ru - all specs in one place