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 и эти API sleep, yield, join; так как suspend не рекомендуется во избежание ситуации, когда поток удерживает блокировку, что приведет к тупику, когда он находится в приостановленном (не запущенном состоянии) состоянии в течение неопределенного времени. Это то же самое поведение и для других API.

Ответ приостановить / резюме будет выполнено в других темах, например t1.suspend() где, как эти API приостановили Thread.currentThread(), Следовательно, пользователь должен предупредить о том, что нужно избегать блокировок перед вызовом этих API, чтобы избежать тупика. Это не тот случай, когда звонят suspend, Поток вызываемого не знает о состоянии (блокировке) потока вызывающего, в котором он собирается выполнить приостановку, следовательно, не рекомендуется.

Я думаю, что это заявление следует рассматривать в полном контексте.

Когда поток вызывает d.wait, он должен владеть внутренней блокировкой для d - в противном случае выдается ошибка. Вызов ожидания внутри синхронизированного метода - простой способ получить внутреннюю блокировку.

Я понимаю, что они должны упростить это так:

Привлечение synchronized методы получает блокировку на объекте, мы можем просто поставить wait() вызов внутри synchronized метод.

Другие вопросы по тегам