Зачем требовать блокировку ждут блокировку
Я сомневаюсь, что в языке Java нам нужно получить блокировку, прежде чем мы ожидаем выполнения какого-либо условия.
Например, блокировка монитора Int Java:
synchronized(lock){
System.out.println("before lock ...");
lock.wait();
System.out.println("after lock ...");
}
или параллелизм использует:
Lock lock = new ReentrantLock();
Condition cond = lock.newCondition();
lock.lock();
try{
System.out.println("before condition ...");
cond.await();
System.out.println("after condition ...");
}catch(Exception e){
e.printStackTrace();
}finally{
lock.unlock();
}
Итак, почему мы не можем ждать, не удерживая замок?
Различаются ли другие языки или это только в Java?
Я надеюсь, что вы можете объяснить причину после проектирования, но не только для определения JAVA-SPEC.
Ответы
Ответ 1
Представьте, что у вас есть что-то, что может потребоваться для потока. Возможно, у вас есть очередь и поток должен ждать, пока в очереди не будет чего-то, чтобы он мог ее обработать. Очередь должна быть потокобезопасной, поэтому она должна быть защищена блокировкой. Вы можете написать следующий код:
- Приобретите замок.
- Убедитесь, что очередь пуста.
- Если очередь пуста, подождите, пока что-то будет помещено в очередь.
К сожалению, это не сработает. Мы держим замок в очереди, так как другая нить может что-то на нем нанести? Попробуйте еще раз:
- Приобретите замок.
- Убедитесь, что очередь пуста.
- Если очередь пуста, отпустите блокировку и подождите, пока что-то будет помещено в очередь.
Упс, теперь у нас все еще есть проблема. Что делать, если после освобождения блокировки, но прежде чем мы дождаемся, что что-то будет помещено в очередь, что-то помещается в очередь? В этом случае мы будем ждать чего-то, что уже произошло.
Существуют переменные условий для решения этой точной проблемы. У них есть атомарная операция "разблокировки и ожидания", которая закрывает это окно.
Таким образом, ожидание должно удерживать блокировку, потому что иначе не было бы способа гарантировать, что вы не ждали того, что уже произошло. Вы должны удерживать замок, чтобы другой поток не участвовал в гонке с вашим ожиданием.
Ответ 2
Ну, чего же мы ждем? Мы ждем, когда условие станет истинным. Другой поток сделает условие истинным, а затем сообщит ожидающие потоки.
Прежде чем вводить ожидание, мы должны проверить, что условие ложно; эта проверка и ожидание должны быть атомарными, то есть под одной и той же блокировкой. В противном случае, если мы введем ожидание, пока условие уже истинно, мы, скорее всего, никогда не пробудимся.
Поэтому необходимо, чтобы блокировка уже была получена до вызова wait()
synchronized(lock)
{
if(!condition)
lock.wait();
Если wait()
автоматически и тихо получает блокировку, многие ошибки будут удалены.
После пробуждения от wait()
мы должны снова проверить условие - нет гарантии, что условие должно стать истинным здесь (по многим причинам - ложное пробуждение, таймаут, прерывание, несколько официантов, несколько условий)
synchronized(lock)
{
if(!condition)
lock.wait();
if(!condition) // check again
...
Как правило, если условие все еще ложно, мы будем ждать снова. Поэтому типичный шаблон
while(!condition)
lock.wait();
Но есть также случаи, когда мы не хотим ждать снова.
Могут ли быть когда-либо законные случаи использования, когда голый wait/notify имеет смысл?
synchronized(lock){ lock.wait(); }
Конечно; приложение может быть составлено с обнаженным wait/notify, с четко определенным поведением; можно утверждать, что это желаемое поведение; и это лучшая реализация для этого поведения.
Однако это не типичный шаблон использования, и нет причин для его учета в дизайне API.
Ответ 3
См. документ для Condition.
A Условие похоже на пул ожидания или ожидание набора объекта, и оно заменяет использование методов мониторинга объекта (wait, notify и notifyAll). Условия позволяют одному потоку приостанавливать выполнение ( "ждать" ), пока не будут уведомлены другим потоком, что некоторое состояние состояния теперь может быть истинным. Экземпляр условия связан с блокировкой так же, как методы мониторинга объекта требуют блокировки общего объекта для ожидания или уведомления. Поэтому перед вызовом await() при условии, поток должен заблокировать объект Lock, который используется для создания условия. Когда вызывается метод wait(), блокировка, связанная с условием, освобождается.
Ответ 4
Если поток просто ждет сигнала для продолжения, есть другие механизмы для этого. Предположительно, существует определенное состояние, защищенное блокировкой, которую поток ожидает, чтобы работать и удовлетворять некоторому условию. Чтобы правильно защитить это состояние, поток должен иметь блокировку до и после ожидания условия, поэтому имеет смысл потребовать получения блокировки.
Ответ 5
Простой ответ потому, что иначе вы получите IllegalMonitorStateException, которое указано в Object.wait javadoc. Внутри, синхронизация в Java использует базовый механизм OS. Таким образом, это не только Java.