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