Java wait()/ join():为什么这不会死锁?

时间:2011-08-30 15:52:50

标签: java multithreading join wait notify

给出以下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(),它被声明为'synchronized',因此它可以调用wait()并安全地读取'mustShutdown';在wait()调用期间,锁定被释放并在返回时重新获取,如wait()文档中所述。一秒钟后,主线程执行shutdown(),再次同步,以便在其他线程正在读取的同时不访问mustShutdown。然后它通过notify()唤醒另一个线程,并通过join()等待它的完成。

但在我看来,其他线程无法从wait()返回,因为它需要在返回之前重新获取线程对象上的锁。它不能这样做因为shutdown()在join()内部仍然保持锁定。为什么它仍能正常工作并正常退出?

3 个答案:

答案 0 :(得分:8)

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()方法等待<() strong>线程对象本身,因此放弃对Thread对象本身的锁定,并且当run()方法也在同一个Thread对象上同步时,您会看到这种意外情况。

答案 1 :(得分:1)

Thread.join的实现使用wait,这使得它可以锁定,这就是为什么它不会阻止其他线程获取锁。

以下是对此示例中发生的事情的逐步说明:

在main方法中启动MyThread线程会导致执行MyThread run方法的新线程。主线程休眠一整秒,让新线程有足够的时间启动并获取MyThread对象的锁定。

然后新线程可以进入wait方法并释放其锁定。此时新线程进入休眠状态,在被唤醒之前不再尝试获取锁定。线程还没有从wait方法返回

此时主线程从休眠状态唤醒,并在MyThread对象上调用shutdown。获取锁没有问题,因为新线程一旦开始等待就会释放它。主线程现在调用notify。进入join方法,主线程检查新线程是否仍处于活动状态,然后等待,释放锁定。

主线程释放锁定后会发生通知。由于新线程在主线程调用notify时处于锁定的等待集中,因此新线程接收通知并唤醒。它可以获取锁,离开wait方法,并完成执行run方法,最后释放锁。

新线程的终止会导致等待其锁定的所有线程都收到通知。这会唤醒主线程,它可以获取锁并检查新线程是否已死,然后它将退出join方法并完成执行。

/**
 * 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;
    }
}
}

答案 2 :(得分:0)

补充其他答案:我没有看到join()在API文档中发布任何锁定,所以这种行为实际上是特定于实现的。

从中学习:

  • 不要继承Thread,而是使用传递给线程对象的Runnable实现。
  • 不要对您没有“拥有”的对象进行同步/等待/通知,例如你不知道还有谁可能同步/等待/通知它。