Простое использование сценария ожидает () и уведомляет () в Java

Я могу получить полный простой сценарий т.е. учебное руководство, которые предлагают, как это должно использоваться, конкретно с Очередью?

175
задан Olaseni 15 August 2013 в 14:58
поделиться

4 ответа

Методы wait() и notify() предназначены для обеспечения механизма, позволяющего потоку блокировать выполнение определенного условия. Для этого я предполагаю, что вы хотите написать реализацию блокирующей очереди, где у вас есть некоторое резервное хранилище элементов фиксированного размера.

Первое, что вам нужно сделать, это определить условия, которых вы хотите, чтобы ждали методы. В данном случае вы хотите, чтобы метод put() блокировал работу до тех пор, пока в хранилище не появится свободное место, а метод take() блокировал работу до тех пор, пока не появится элемент для возврата.

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public synchronized void put(T element) throws InterruptedException {
        while(queue.size() == capacity) {
            wait();
        }

        queue.add(element);
        notify(); // notifyAll() for multiple producer/consumer threads
    }

    public synchronized T take() throws InterruptedException {
        while(queue.isEmpty()) {
            wait();
        }

        T item = queue.remove();
        notify(); // notifyAll() for multiple producer/consumer threads
        return item;
    }
}

Есть несколько моментов, которые следует отметить относительно того, как вы должны использовать механизмы wait и notify.

Во-первых, необходимо убедиться, что любые вызовы wait() или notify() находятся в синхронизированной области кода (причем вызовы wait() и notify() должны быть синхронизированы на одном и том же объекте). Причиной этого (помимо стандартных проблем безопасности потоков) является так называемый пропущенный сигнал.

Примером этого может служить то, что поток может вызвать put(), когда очередь оказывается заполненной, затем он проверяет условие, видит, что очередь заполнена, но прежде чем он сможет заблокировать ее, будет запланирован другой поток. Затем этот второй поток take() берет элемент из очереди и уведомляет ожидающие потоки о том, что очередь больше не заполнена. Однако, поскольку первый поток уже проверил условие, он просто вызовет wait() после повторной синхронизации, хотя и сможет продвинуться вперед.

С помощью синхронизации на общем объекте вы можете гарантировать, что эта проблема не возникнет, поскольку вызов take() второго потока не сможет выполнить, пока первый поток не заблокируется.

Во-вторых, вам нужно поместить проверяемое условие в цикл while, а не в оператор if, из-за проблемы, известной как ложное пробуждение. Это когда ожидающий поток иногда может быть повторно активирован без вызова notify(). Помещение этой проверки в цикл while гарантирует, что если произойдет ложное пробуждение, условие будет перепроверено, и поток снова вызовет wait().


Как уже упоминалось в некоторых других ответах, в Java 1.5 появилась новая библиотека параллелизма (в пакете java.util.concurrent), которая была разработана для обеспечения более высокого уровня абстракции механизма ожидания/оповещения. Используя эти новые возможности, вы можете переписать исходный пример следующим образом:

public class BlockingQueue<T> {

    private Queue<T> queue = new LinkedList<T>();
    private int capacity;
    private Lock lock = new ReentrantLock();
    private Condition notFull = lock.newCondition();
    private Condition notEmpty = lock.newCondition();

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    public void put(T element) throws InterruptedException {
        lock.lock();
        try {
            while(queue.size() == capacity) {
                notFull.await();
            }

            queue.add(element);
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while(queue.isEmpty()) {
                notEmpty.await();
            }

            T item = queue.remove();
            notFull.signal();
            return item;
        } finally {
            lock.unlock();
        }
    }
}

Конечно, если вам действительно нужна блокирующая очередь, то вы должны использовать реализацию интерфейса BlockingQueue.

Кроме того, для подобных примеров я бы очень рекомендовал Java Concurrency in Practice, поскольку он охватывает все, что вы хотите знать о проблемах и решениях, связанных с параллелизмом.

267
ответ дан 23 November 2019 в 20:25
поделиться

Несмотря на то, что вы просили конкретно wait () и notify () , я считаю, что эта цитата все еще достаточно важно:

Джош Блох, Эффективное Java 2-е издание , пункт 69: Предпочитайте утилиты параллелизма wait и notify (выделено им):

Учитывая сложность правильного использования wait и notify , вам следует использовать утилиты параллелизма более высокого уровня вместо [...], используя wait и notify напрямую похоже на программирование на "языке ассемблера параллелизма" по сравнению с языком более высокого уровня, предоставляемым java.util.concurrent . Использование wait и notify в новом коде встречается редко, если вообще возникает.

35
ответ дан 23 November 2019 в 20:25
поделиться

Вы видели это Учебное пособие по Java ?

Кроме того, я бы посоветовал вам держаться подальше от игры с подобными вещами в реальном программном обеспечении. С этим хорошо поиграть, чтобы вы знали, что это такое, но у параллелизма повсюду есть подводные камни. Если вы создаете программное обеспечение для других людей, лучше использовать абстракции более высокого уровня и синхронизированные коллекции или очереди JMS.

По крайней мере, я так делаю. Я не эксперт по параллелизму, поэтому по возможности стараюсь не обрабатывать потоки вручную.

6
ответ дан 23 November 2019 в 20:25
поделиться

Не пример очереди, но очень простой :)

class MyHouse {
    private boolean pizzaArrived = false;

    public void eatPizza(){
        synchronized(this){
            while(!pizzaArrived){
                wait();
            }
        }
        System.out.println("yumyum..");
    }

    public void pizzaGuy(){
        synchronized(this){
             this.pizzaArrived = true;
             notifyAll();
        }
    }
}

Некоторые важные моменты:
1) НИКОГДА не делайте

 if(!pizzaArrived){
     wait();
 }

Всегда используйте while (условие), потому что

  • a) потоки могут время от времени пробуждающий от состояния ожидания без чьего-либо уведомления. (даже если пицца не звонит, кто-нибудь решает попробовать пиццу ).
  • б) Вы должны снова проверить условие после получения синхронизированной блокировки . Допустим, пицца не длится вечно. Вы просыпаетесь, очередь на пиццу, но этого не хватит на всех. Если вы не проверяете, вы можете съесть бумагу! :) (возможно, лучшим примером будет while (! PizzaExists) {wait ();} .

2) Вы должны удерживать блокировку (синхронизировано) перед вызов wait / nofity. Перед пробуждением потоки также должны получить блокировку.

3) Старайтесь избегать получения какой-либо блокировки в синхронизированном блоке и старайтесь не вызывать чужие методы (методы, о которых вы точно не знаете, что они делают). Если необходимо, обязательно примите меры, чтобы избежать взаимоблокировок.

4) Будьте осторожны с notify (). Придерживайтесь notifyAll (), пока не узнаете, что делаете.

5) И последнее, но не менее важное: прочтите Java Concurrency in Practice !

147
ответ дан 23 November 2019 в 20:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: