Могу ли я получить полный простой сценарий, то есть учебник, который предлагает, как это следует использовать, особенно с очередью?
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 , поскольку он охватывает все, что вы хотели бы знать о проблемах и решениях, связанных с параллелизмом.
notify
пробуждает только одну нить. Если два потребительских потока конкурируют за удаление элемента, одно уведомление может разбудить другой потребительский поток, который не может ничего с этим поделать и вернется в спящий режим (вместо производителя, который, как мы надеялись, вставил новый элемент). поток производителя не проснулся, ничего не вставлено, и теперь все три потока будут бездействовать бесконечно. Я удалил свой предыдущий комментарий, так как в нем говорилось (ошибочно), что причиной проблемы является ложное пробуждение (это не так)Не пример очереди, но очень просто :)
Некоторые важные моменты:
1) НИКОГДА не делайте
Всегда используйте while (условие), потому что
while(!pizzaExists){ wait(); }
.2) Вы должны удерживать блокировку (синхронизированную) перед вызовом wait / nofity. Потоки также должны получить блокировку перед пробуждением.
3) Старайтесь избегать получения какой-либо блокировки в вашем синхронизированном блоке и старайтесь не вызывать инопланетные методы (методы, которые вы точно не знаете, что они делают). Если вам нужно, обязательно примите меры, чтобы избежать тупиков.
4) Будьте осторожны с уведомлением (). Придерживайтесь notifyAll (), пока не узнаете, что делаете.
5) И последнее, но не менее важное: прочитайте Java Concurrency на практике !
источник
pizzaArrived
флаг? если флаг будет изменен без обращения кnotify
нему, это не будет иметь никакого эффекта. Также просто сwait
иnotify
звонками пример работает.synchronized
ключевым словом, объявлять переменную избыточноvolatile
, и рекомендуется избегать ее, чтобы избежать путаницы @mridaДаже если вы просили
wait()
иnotify()
конкретно, я чувствую , что эта цитата еще достаточно важно:Джош Блох, Effective Java 2nd Edition , Item 69: Предпочитают утилиты параллелизма
wait
иnotify
(подчеркивают его):источник
notify()
иwait()
сноваВы смотрели на этот учебник Java ?
Кроме того, я бы посоветовал вам держаться подальше от игры с такими вещами в реальном программном обеспечении. Хорошо играть с ним, чтобы вы знали, что это такое, но параллелизм повсеместен. Лучше использовать абстракции более высокого уровня и синхронизированные коллекции или очереди JMS, если вы создаете программное обеспечение для других людей.
Это по крайней мере то, что я делаю. Я не эксперт по параллелизму, поэтому я стараюсь избегать обработки потоков вручную везде, где это возможно.
источник
пример
источник
Пример для wait () и notifyall () в Threading.
Синхронизированный список статических массивов используется как ресурс, и метод wait () вызывается, если список массивов пуст. Метод notify () вызывается после добавления элемента в список массивов.
источник
if(arrayList.size() == 0)
, я думаю, что это может быть ошибкой здесь.