Ответы:
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 , поскольку он охватывает все, что вы хотели бы знать о проблемах и решениях, связанных с параллелизмом.
Не пример очереди, но очень просто :)
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 на практике !
pizzaArrived
флаг? если флаг будет изменен без обращения к notify
нему, это не будет иметь никакого эффекта. Также просто с wait
и notify
звонками пример работает.
synchronized
ключевым словом, объявлять переменную избыточно volatile
, и рекомендуется избегать ее, чтобы избежать путаницы @mrida
Даже если вы просили wait()
и notify()
конкретно, я чувствую , что эта цитата еще достаточно важно:
Джош Блох, Effective Java 2nd Edition , Item 69: Предпочитают утилиты параллелизма wait
и notify
(подчеркивают его):
Учитывая трудность использования
wait
иnotify
правильность, вы должны использовать высокоуровневые утилиты параллелизма вместо [...] использованияwait
иnotify
напрямую, как программирование на «языке ассемблера параллелизма», по сравнению с языком более высокого уровня, предоставляемымjava.util.concurrent
. Существует редкая, если вообще когда-либо, причина для использованияwait
иnotify
в новом коде .
notify()
и wait()
снова
Вы смотрели на этот учебник Java ?
Кроме того, я бы посоветовал вам держаться подальше от игры с такими вещами в реальном программном обеспечении. Хорошо играть с ним, чтобы вы знали, что это такое, но параллелизм повсеместен. Лучше использовать абстракции более высокого уровня и синхронизированные коллекции или очереди JMS, если вы создаете программное обеспечение для других людей.
Это по крайней мере то, что я делаю. Я не эксперт по параллелизму, поэтому я стараюсь избегать обработки потоков вручную везде, где это возможно.
пример
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
}
}
Пример для 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());
}
}
if(arrayList.size() == 0)
, я думаю, что это может быть ошибкой здесь.
notify
пробуждает только одну нить. Если два потребительских потока конкурируют за удаление элемента, одно уведомление может разбудить другой потребительский поток, который не может ничего с этим поделать и вернется в спящий режим (вместо производителя, который, как мы надеялись, вставил новый элемент). поток производителя не проснулся, ничего не вставлено, и теперь все три потока будут бездействовать бесконечно. Я удалил свой предыдущий комментарий, так как в нем говорилось (ошибочно), что причиной проблемы является ложное пробуждение (это не так)