Ответ 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 связанные проблемы и решения.