Короткий ответ, Banthar is correct (+1 от меня). Независимо от того, в какой порядок вы их введете, уведомление не будет отправлено до тех пор, пока уведомляющий поток не покинет синхронизированный блок. См. the Java Language Specification (добавлено мной):
Нет гарантии, какая нить в наборе ожидания выбрана. Это удаление из набора ожидания позволяет возобновить u в действии ожидания. Обратите внимание, однако, что действия блокировки u при возобновлении не могут быть успешными до тех пор, пока t полностью не разблокирует монитор для m.
Когда вы новичок в написании многопоточного кода Java, есть некоторые проблемы, с которыми нужно справиться. Код примера, на который вы ссылаетесь, не справился с некоторыми из этих проблем.
Существует совместное планирование и упреждающее планирование: совместное означает, что потоки приносят добровольно в момент их собственного выбора, упреждающее означает, что планировщик может в любой момент обменивать потоки. Первоначально, когда Java выходила, некоторые платформы были совместными, а другие были упреждающими, и код должен был быть написан для запуска под обеими моделями, если вы хотите, чтобы он был межплатформенным. Для понимания того, как использовать wait и notify, вы должны принять упреждающую модель (потому что это преобладающая модель, и это также ваш худший случай: вы не можете рассчитывать на то, что потоки будут играть хорошо).
Предварительное изъятие может произойти в любое время, и последствие будет заключаться в том, что если есть промежуток между временем, в течение которого ваш поток проверяет что-либо, и временем, которое оно воздействует на результаты этой проверки, когда ваша нить не удерживала блокировку, результат проверки устарел, и ваши действия могут быть недействительными. Когда примерный код проверяет размер очереди, он получает блокировку в очереди, в промежуток времени между этой проверкой и временем, которое занимает блокировка, другой поток мог действовать, и размер очереди мог быть изменен. Вот почему Nicolas is giving you the advice to handle the locking like this:
synchronized (sharedQueue) {
while (sharedQueue.isEmpty()) {
System.out.println("Queue is empty " + Thread.currentThread().getName()
+ " is waiting , size: " + sharedQueue.size());
sharedQueue.wait();
}
}
Что здесь происходит, что поток получает общую блокировку, и только затем проверяет, если очередь пуста. Если очередь пуста, для потока нечего делать, поэтому он освобождает блокировку и переходит в режим ожидания, пока не будет уведомлен. Затем, когда он получает уведомление, он просыпается, восстанавливает блокировку (она должна сделать это до выхода из метода ожидания) и продолжает проверять, снова ли очередь снова в верхней части цикла while. Он должен снова проверить (с заблокированной задержкой), поскольку он не может предположить, что очередь должна быть пустой на основании того, что она была уведомлена: другой поток мог залезть и действовать в промежуток времени между уведомлением об ожидающем потоке и когда он приобрел замок.
Если возникает вопрос, почему код вызывает notifyAll и не уведомляет об этом, это связано с тем, что уведомление только вызывает выбор одного потока, и этот поток может быть либо потребителем, либо производителем и, возможно, не сможет эффективно сделать что-либо , в зависимости от ситуации. Таким образом, вместо этого код пробуждает все потоки, которые пытаются получить блокировку, и позволяет им бороться с этим. Не самая эффективная вещь, но она работает.
В итоге:
Действуя на основе чеков, когда блокировка не удерживается подвержен ошибкам.
Ожидающие потоки освобождают замок, и между уведомлением и блокировкой не возникает атомарной связи.
Вы не можете предположить, что какой-либо конкретный поток выбран для уведомления.
Дополнительную информацию см. На сайте the Oracle tutorial.
Большое изображение, потоки не должны отвечать за принятие решений о блокировке, поскольку это небезопасно: введение одной плохой нити может повредить структуру данных для всех. Структуры данных должны защищать себя от повреждения из-за одновременного доступа. Оберните свою очередь в потоковом объекте, который заботится обо всех блокировках, ожиданиях и уведомлениях.Например, если я ограниченные очереди, которая инкапсулирует список:
class MyBoundedQueue<T> {
private final Object lock = new Object();
private final int upperBound;
public List mylist = new ArrayList();
public MyBoundedQueue(int upperBound) {
this.upperBound = upperBound;
}
public T take() throws InterruptedException {
synchronized(lock) {
while (mylist.isEmpty()) {
lock.wait();
}
lock.notifyAll();
return mylist.remove(0);
}
}
public void put(T t) throws InterruptedException {
synchronized(lock) {
while (mylist.size() == upperBound) {
lock.wait();
}
mylist.add(t);
lock.notifyAll();
}
}
}
то потребитель становится проще (мы можем удалить потреблять метод), став
class Consumer implements Runnable {
private MyBoundedQueue<Integer> sharedQueue;
public Consumer(MyBoundedQueue<Integer> sharedQueue) {
this.sharedQueue = sharedQueue;
}
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("consumed: " + sharedQueue.take());
Thread.sleep(50L);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
и также продюсер будет выглядеть
class Producer implements Runnable {
private MyBoundedQueue<Integer> sharedQueue;
public Producer(MyBoundedQueue<Integer> sharedQueue) {
this.sharedQueue = sharedQueue;
}
public void run() {
try {
for (int i = 0; i < 7; i++) {
sharedQueue.put(i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
Таким образом, нити приобретают замки, но структура данных осуществляется доступ, определяет, что должно быть заблокировано. Кроме того, BoundedQueue хранит заказ, о котором вы изначально спросили, поэтому для удобства удалить последний, чтобы избежать необходимости создавать локальную переменную для ссылки на элемент, извлеченный из списка. Единственное значение - разборчивость, есть одно меньшее имя локальной переменной, которое нужно читать.
отличный ответ. ваш последний абзац немного запутан, так как различие между «нитями, делающими блокировку» и «объектами, делающими блокировку», является своего рода туманным. Я предполагаю, что вы пытаетесь сделать вывод о внешней блокировке и внутренней блокировке (т. е. сохранить блокировку, инкапсулированную внутри владельца структуры данных). – jtahlborn
@jahlborn: большое спасибо. я попытаюсь переписать последний абзац, чтобы сделать его более ясным и добавить пример. –