Я могу получить полный простой сценарий т.е. учебное руководство, которые предлагают, как это должно использоваться, конкретно с Очередью?
Методы 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, поскольку он охватывает все, что вы хотите знать о проблемах и решениях, связанных с параллелизмом.
Несмотря на то, что вы просили конкретно wait ()
и notify ()
, я считаю, что эта цитата все еще достаточно важно:
Джош Блох, Эффективное Java 2-е издание , пункт 69: Предпочитайте утилиты параллелизма wait
и notify
(выделено им):
Учитывая сложность правильного использования
wait
иnotify
, вам следует использовать утилиты параллелизма более высокого уровня вместо [...], используяwait
иnotify
напрямую похоже на программирование на "языке ассемблера параллелизма" по сравнению с языком более высокого уровня, предоставляемымjava.util.concurrent
. Использованиеwait
иnotify
в новом коде встречается редко, если вообще возникает.
Вы видели это Учебное пособие по Java ?
Кроме того, я бы посоветовал вам держаться подальше от игры с подобными вещами в реальном программном обеспечении. С этим хорошо поиграть, чтобы вы знали, что это такое, но у параллелизма повсюду есть подводные камни. Если вы создаете программное обеспечение для других людей, лучше использовать абстракции более высокого уровня и синхронизированные коллекции или очереди JMS.
По крайней мере, я так делаю. Я не эксперт по параллелизму, поэтому по возможности стараюсь не обрабатывать потоки вручную.
Не пример очереди, но очень простой :)
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) Будьте осторожны с notify (). Придерживайтесь notifyAll (), пока не узнаете, что делаете.
5) И последнее, но не менее важное: прочтите Java Concurrency in Practice !