Java: делает ожидание () снять блокировку с синхронизированного блока
У меня сложилось впечатление, что wait() снимает все блокировки, но я нашел этот пост, который говорит
"Вызов ожидания внутри синхронизированного метода - простой способ получить внутреннюю блокировку"
Пожалуйста, уточните, я немного запутался.
http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
4 ответа
"Вызов ожидания внутри синхронизированного метода - простой способ получить внутреннюю блокировку"
Это предложение неверно, это ошибка в документации.
Поток получает внутреннюю блокировку, когда входит в синхронизированный метод. Поток внутри синхронизированного метода устанавливается как владелец блокировки и находится в состоянии RUNNABLE. Любой поток, который пытается войти в заблокированный метод, становится заблокированным.
Когда поток вызывает ожидание, он снимает текущую блокировку объекта (он сохраняет все блокировки от других объектов) и затем переходит в состояние ОЖИДАНИЯ.
Когда какой-то другой поток вызывает notify или notifyAll для того же объекта, первый поток изменяет состояние с WAITING на BLOCKED, уведомленный поток НЕ автоматически восстанавливает блокировку или становится RUNNABLE, фактически он должен бороться за блокировку со всеми другими заблокированными потоками.
Оба состояния WAITING и BLOCKED препятствуют выполнению потока, но они очень разные.
ПЕРЕДАЮЩИЕ потоки должны быть явно преобразованы в ЗАБЛОКИРОВАННЫЕ потоки путем уведомления от какого-либо другого потока.
ОЖИДАНИЕ никогда не переходит непосредственно в RUNNABLE.
Когда поток RUNNABLE снимает блокировку (оставляя монитор или ожидая), один из потоков BLOCKED автоматически занимает свое место.
Таким образом, чтобы подвести итог, поток получает блокировку, когда он входит в синхронизированный метод или когда он повторно входит в синхронизированный метод после ожидания.
public synchronized guardedJoy() {
// must get lock before entering here
while(!joy) {
try {
wait(); // releases lock here
// must regain the lock to reentering here
} catch (InterruptedException e) {}
}
System.out.println("Joy and efficiency have been achieved!");
}
Я подготовил небольшой тестовый класс (какой-то очень грязный код, извините), чтобы продемонстрировать, что ожидание фактически снимает блокировку.
public class Test {
public static void main(String[] args) throws Exception {
testCuncurrency();
}
private static void testCuncurrency() throws InterruptedException {
Object lock = new Object();
Thread t1 = new Thread(new WaitTester(lock));
Thread t2 = new Thread(new WaitTester(lock));
t1.start();
t2.start();
Thread.sleep(15 * 1000);
synchronized (lock) {
System.out.println("Time: " + new Date().toString()+ ";" + "Notifying all");
lock.notifyAll();
}
}
private static class WaitTester implements Runnable {
private Object lock;
public WaitTester(Object lock) {
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
System.out.println(getTimeAndThreadName() + ":only one thread can be in synchronized block");
Thread.sleep(5 * 1000);
System.out.println(getTimeAndThreadName() + ":thread goes into waiting state and releases the lock");
lock.wait();
System.out.println(getTimeAndThreadName() + ":thread is awake and have reacquired the lock");
System.out.println(getTimeAndThreadName() + ":syncronized block have finished");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private static String getTimeAndThreadName() {
return "Time: " + new Date().toString() + ";" + Thread.currentThread().getName();
}
}
Запуск этого класса на моей машине возвращает следующий результат:
Time: Tue Mar 29 09:16:37 EEST 2016;Thread-0:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-0:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:42 EEST 2016;Thread-1:only one thread can be in synchronized block
Time: Tue Mar 29 09:16:47 EEST 2016;Thread-1:thread goes into waiting state and releases the lock
Time: Tue Mar 29 09:16:52 EEST 2016;Notifying all
Time: Tue Mar 29 09:16:52 EEST 2016;Thread-1:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-1:syncronized block have finished
Time: Tue Mar 29 09:16:57 EEST 2016;Thread-0:thread is awake and have reacquired the lock
Time: Tue Mar 29 09:17:02 EEST 2016;Thread-0:syncronized block have finished
wait
:: это частьjava.lang.Object
класс, поэтому мы можем вызвать этот метод только на объекте. для вызова этого необходим монитор (блокировка) этого объекта, иначеIllegalMonitorStateException
будет сгенерировано, например) Thread.currentThread().wait() сгенерирует это исключение в приведенном ниже коде.Example1 public void doSomething() { Line 1 synchronized(lockObject) { //lock acquired Line 2 lockObject.wait(); // NOT Thread.currentThread().wait() Line 3 } }
Теперь вызов wait в строке 3 освободит блокировку, полученную в строке 2. Таким образом, любой другой поток вошел в линию 1 и ожидает получения блокировки
lockObject
приобретет этот замок и продолжит.Теперь давайте рассмотрим это
Example2
; только здесьlockObject2
блокировка снята, а текущий поток все еще держитlockObject1
замок. Это приведет к тупику; Поэтому пользователь должен быть более осторожным в этом случае.Example2 public void doSomething() { Line 1 synchronized(lockObject1) { //lock1 acquired Line 2 synchronized(lockObject2) { //lock2 acquired Line 3 lockObject2.wait(); Line 4 } } }
Если это ожидание заменяется на
sleep, yield, or join
у них нет возможности освободить замок. Только ожидание может снять блокировку, которую он держит.Просто осторожнее
t1.sleep()/t1.yield()
где статические API, и всегда действие будет выполняться наcurrentThread
не по темеt1
,Тогда давайте поймем, в чем разница между
suspend
и эти APIsleep, yield, join
; так какsuspend
не рекомендуется во избежание ситуации, когда поток удерживает блокировку, что приведет к тупику, когда он находится в приостановленном (не запущенном состоянии) состоянии в течение неопределенного времени. Это то же самое поведение и для других API.Ответ приостановить / резюме будет выполнено в других темах, например
t1.suspend()
где, как эти API приостановилиThread.currentThread()
, Следовательно, пользователь должен предупредить о том, что нужно избегать блокировок перед вызовом этих API, чтобы избежать тупика. Это не тот случай, когда звонятsuspend
, Поток вызываемого не знает о состоянии (блокировке) потока вызывающего, в котором он собирается выполнить приостановку, следовательно, не рекомендуется.
Я думаю, что это заявление следует рассматривать в полном контексте.
Когда поток вызывает d.wait, он должен владеть внутренней блокировкой для d - в противном случае выдается ошибка. Вызов ожидания внутри синхронизированного метода - простой способ получить внутреннюю блокировку.
Я понимаю, что они должны упростить это так:
Привлечение
synchronized
методы получает блокировку на объекте, мы можем просто поставитьwait()
вызов внутриsynchronized
метод.