Простой сценарий с использованием wait () и notify () в Java


181

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

Ответы:


269

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()вызовы синхронизируются на одном объекте). Причиной этого (кроме стандартных проблем безопасности потоков) является то, что называется пропущенным сигналом.

Примером этого является то, что поток может вызвать, 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 , поскольку он охватывает все, что вы хотели бы знать о проблемах и решениях, связанных с параллелизмом.


7
@greuze, notifyпробуждает только одну нить. Если два потребительских потока конкурируют за удаление элемента, одно уведомление может разбудить другой потребительский поток, который не может ничего с этим поделать и вернется в спящий режим (вместо производителя, который, как мы надеялись, вставил новый элемент). поток производителя не проснулся, ничего не вставлено, и теперь все три потока будут бездействовать бесконечно. Я удалил свой предыдущий комментарий, так как в нем говорилось (ошибочно), что причиной проблемы является ложное пробуждение (это не так)
finnw

1
@finnw Насколько я могу судить, обнаруженная вами проблема может быть решена с помощью notifyAll (). Я прав?
Клинт Иствуд

1
Пример, приведенный здесь @Jared, довольно хорош, но имеет серьезное падение. В коде все методы были помечены как синхронизированные, но ДВУХ СИНХРОНИЗИРОВАННЫХ МЕТОДОВ НЕ МОЖЕТ БЫТЬ ВЫПОЛНЕНО В ТО ЖЕ ВРЕМЯ, тогда как получается, что на рисунке есть второй поток.
Шивам Аггарвал

10
@ Brut3Forc3 вам нужно прочитать javadoc wait (): там написано: поток освобождает владельца этого монитора . Таким образом, как только вызывается wait (), монитор освобождается, и другой поток может выполнить другой синхронизированный метод очереди.
JB Низет

1
@JBNizet. «Примером этого является то, что поток может вызвать put (), когда очередь оказывается заполненной, затем он проверяет условие, видит, что очередь заполнена, однако, прежде чем он сможет блокировать другой поток, запланирован». второй поток запланирован, если ожидание еще не было
вызвано

148

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

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 (условие), потому что

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

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

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

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

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


1
Не могли бы вы рассказать, почему бы не использовать "if (! PizzaArrived) {wait ();}"?
все

2
@ Каждый: добавлено объяснение. НТН.
Энно Сиодзи

1
зачем использовать pizzaArrivedфлаг? если флаг будет изменен без обращения к notifyнему, это не будет иметь никакого эффекта. Также просто с waitи notifyзвонками пример работает.
Пабло Фернандес

2
Я не понимаю - поток 1 выполняет метод eatPizza () и входит в верхний синхронизированный блок и синхронизируется с классом MyHouse. Пиццы еще не было, так что она просто ждет. Теперь поток 2 пытается доставить пиццу, вызвав метод pizzaGuy (); но не может, поскольку поток 1 уже владеет блокировкой, и он не отказывается от нее (она постоянно ожидает). В результате получается тупик - поток 1 ожидает, пока поток 2 выполнит метод notifyAll (), а поток 2 ожидает, когда поток 1 снимет блокировку с класса MyHouse ... Что я пропускаю Вот?
flamming_python

1
Нет, когда переменная защищена synchronizedключевым словом, объявлять переменную избыточно volatile, и рекомендуется избегать ее, чтобы избежать путаницы @mrida
Enno Shioji

37

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

Джош Блох, Effective Java 2nd Edition , Item 69: Предпочитают утилиты параллелизма waitи notify(подчеркивают его):

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


BlockingQueueS, предоставляемый в пакете java.util.concurrent, не является постоянным. Что мы можем использовать, когда очередь должна быть постоянной? то есть, если система выходит из строя с 20 элементами в очереди, мне нужно, чтобы они присутствовали при перезагрузке системы. Поскольку все очереди java.util.concurrent кажутся «в памяти», есть ли какой-нибудь способ, которым они могут быть использованы как / hacked / overridden для обеспечения реализаций, поддерживающих постоянство?
Volksman

1
Возможно, может быть обеспечена очередь поддержки? то есть мы предоставили бы реализацию интерфейса очереди, которая является постоянной.
Volksman

Это очень хорошо, чтобы упомянуть в этом контексте, что вам никогда не придется использовать notify()и wait()снова
Chaklader Asfak Arefe

7

Вы смотрели на этот учебник Java ?

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

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


2

пример

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla bla bla
        }
     }
     public synchronized void threadCondWait(){
        while(myCondition){
           wait();//Comminucate with notify()
        }
     }

}
public class myAnotherThread extends Thread{
     @override
     public void run(){
        //Bla Bla bla
        notify();//Trigger wait() Next Step
     }

}

0

Пример для wait () и notifyall () в Threading.

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

public class PrinterResource extends Thread{

//resource
public static List<String> arrayList = new ArrayList<String>();

public void addElement(String a){
    //System.out.println("Add element method "+this.getName());
    synchronized (arrayList) {
        arrayList.add(a);
        arrayList.notifyAll();
    }
}

public void removeElement(){
    //System.out.println("Remove element method  "+this.getName());
    synchronized (arrayList) {
        if(arrayList.size() == 0){
            try {
                arrayList.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }else{
            arrayList.remove(0);
        }
    }
}

public void run(){
    System.out.println("Thread name -- "+this.getName());
    if(!this.getName().equalsIgnoreCase("p4")){
        this.removeElement();
    }
    this.addElement("threads");

}

public static void main(String[] args) {
    PrinterResource p1 = new PrinterResource();
    p1.setName("p1");
    p1.start();

    PrinterResource p2 = new PrinterResource();
    p2.setName("p2");
    p2.start();


    PrinterResource p3 = new PrinterResource();
    p3.setName("p3");
    p3.start();


    PrinterResource p4 = new PrinterResource();
    p4.setName("p4");
    p4.start();     

    try{
        p1.join();
        p2.join();
        p3.join();
        p4.join();
    }catch(InterruptedException e){
        e.printStackTrace();
    }
    System.out.println("Final size of arraylist  "+arrayList.size());
   }
}

1
Пожалуйста, дважды проверьте эту строку if(arrayList.size() == 0), я думаю, что это может быть ошибкой здесь.
Wizmann
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.