Java wait () / join (): почему это не тупик? - PullRequest
9 голосов
/ 30 августа 2011

Учитывая следующий код Java:

public class Test {

    static private class MyThread extends Thread {
        private boolean mustShutdown = false;

        @Override
        public synchronized void run() {
            // loop and do nothing, just wait until we must shut down
            while (!mustShutdown) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    System.out.println("Exception on wait()");
                }
            }
        }

        public synchronized void shutdown() throws InterruptedException {
            // set flag for termination, notify the thread and wait for it to die
            mustShutdown = true;
            notify();
            join(); // lock still being held here, due to 'synchronized'
        }
    }

    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.start();

        try {
            Thread.sleep(1000);
            mt.shutdown();
        } catch (InterruptedException e) {
            System.out.println("Exception in main()");
        }
    }
}

Запуск этого будет ждать одну секунду, а затем правильно завершится.Но для меня это неожиданно, я ожидаю, что здесь произойдет взаимоблокировка.

Мои рассуждения таковы: вновь созданный MyThread выполнит run (), который объявлен как «синхронизированный», поэтомуможет вызвать wait () и безопасно прочитать «mustShutdown»;во время этого вызова wait () блокировка снимается и возвращается после возврата, как описано в документации по wait ().Через одну секунду главный поток выполняет shutdown (), которая снова синхронизируется, чтобы не обращаться к mustShutdown одновременно с тем, как он читается другим потоком.Затем он пробуждает другой поток с помощью notify () и ожидает его завершения с помощью join ().

Но, по моему мнению, другой поток никогда не сможет вернуться из wait (), так как онперед возвращением необходимо повторно получить блокировку объекта потока.Это не может быть сделано, потому что shutdown () по-прежнему удерживает блокировку внутри join ().Почему он все еще работает и правильно выходит?

Ответы [ 3 ]

8 голосов
/ 30 августа 2011

join () метод внутренне вызывает wait () , что приведет к снятию блокировки (объекта Thread).

См. Код join () ниже:

public final synchronized void join(long millis) 
    throws InterruptedException {
    ....
    if (millis == 0) {
       while (isAlive()) {
         wait(0);  //ends up releasing lock
       }
    }
    ....
}

Причина, по которой ваш код видит это, а не видит вообще: : Причина, по которой ваш код видит это и не наблюдается вообще, заключается в том, что метод join () waits () в Потоковый объект сам и, следовательно, снимает блокировку с самого объекта Thread, и поскольку ваш метод run () также синхронизируется с тем же объектом Thread, вы видите этот неожиданный в противном случае сценарий.

1 голос
/ 30 августа 2011

Реализация Thread.join использует ожидание, которое позволяет снять блокировку, поэтому не мешает другому потоку получить блокировку.

Вот пошаговое описание того, что происходит в этом примере:

Запуск потока MyThread в методе main приводит к тому, что новый поток выполняет метод выполнения MyThread.Основной поток спит целую секунду, давая новому потоку достаточно времени для запуска и получения блокировки объекта MyThread.

Новый поток может затем ввести метод ожидания и снять блокировку.В этот момент новый поток отключается, он не будет пытаться снова получить блокировку, пока он не проснется.Поток еще не возвращает из метода ожидания.

В этот момент основной поток выходит из спящего режима и вызывает завершение работы объекта MyThread.У него нет проблем с получением блокировки, потому что новый поток освободил его, как только начал ждать.Основной поток вызывает уведомления сейчас.При входе в метод соединения основной поток проверяет, что новый поток все еще жив, а затем ждет, сняв блокировку.

Уведомление происходит, когда основной поток снимает блокировку.Поскольку новый поток находился в состоянии ожидания, установленном для блокировки, в тот момент, когда основной поток вызвал notify, новый поток получает уведомление и активируется.Он может получить блокировку, выйти из метода ожидания и завершить выполнение метода run, наконец сняв блокировку.

Завершение нового потока приводит к тому, что все потоки, ожидающие его блокировки, получают уведомление.Это пробуждает основной поток, он может получить блокировку и проверить, что новый поток мертв, затем он выйдет из метода соединения и завершит выполнение.

/**
 * Waits at most <code>millis</code> milliseconds for this thread to 
 * die. A timeout of <code>0</code> means to wait forever. 
 *
 * @param      millis   the time to wait in milliseconds.
 * @exception  InterruptedException if any thread has interrupted
 *             the current thread.  The <i>interrupted status</i> of the
 *             current thread is cleared when this exception is thrown.
 */
public final synchronized void join(long millis) 
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
    while (isAlive()) {
    wait(0);
    }
} else {
    while (isAlive()) {
    long delay = millis - now;
    if (delay <= 0) {
        break;
    }
    wait(delay);
    now = System.currentTimeMillis() - base;
    }
}
}
0 голосов
/ 30 августа 2011

В дополнение к другим ответам: я не вижу упоминания о join() снятии каких-либо блокировок в API-документации, так что это поведение на самом деле зависит от реализации.

Учитесь на этом:

  • не делите подкласс Thread, вместо этого используйте реализацию Runnable, переданную вашему объекту потока.
  • не синхронизируйте / ждите / не уведомляйте объекты, которые вам не "принадлежат", напримергде вы не знаете, кто еще может синхронизировать / ждать / уведомить об этом.
...