Простой сценарий с использованием wait () и notify () в Java - PullRequest
170 голосов
/ 29 марта 2010

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

Ответы [ 6 ]

262 голосов
/ 29 марта 2010

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

146 голосов
/ 29 марта 2010

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

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 на практике !

34 голосов
/ 29 марта 2010

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

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

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

6 голосов
/ 29 марта 2010

Вы ознакомились с этим Учебником Java ?

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

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

2 голосов
/ 21 октября 2015

Пример

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 голосов
/ 22 марта 2016

Пример для 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());
   }
}
...