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

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

Ответы

Ответ 1

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

Ответ 2

Не пример очереди, но чрезвычайно простой:)

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 (condition), потому что

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

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

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

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

5) Наконец, прочитайте Java Concurrency на практике!

Ответ 3

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

Джош Блох, эффективный Java 2nd Edition, пункт 69: Предпочитают утилиты concurrency для wait и notify (акцент его):

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

Ответ 4

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

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

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

Ответ 5

Пример

public class myThread extends Thread{
     @override
     public void run(){
        while(true){
           threadCondWait();// Circle waiting...
           //bla bla
        }
     }
     public syncronized 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
     }

}

Ответ 6

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