11

给定以下 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() 中仍然持有锁。为什么它仍然可以正常工作并正常退出?

4

3 回答 3

9

join()方法在内部调用wait(),这将导致释放锁(线程对象)。

请参见下面的 join() 代码:

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

你的代码看到这个而不是一般看到的原因: : 你的代码看到这个而不是一般没有观察到的原因是因为 join() 方法在Thread 对象本身上等待() 并因此放弃对 Thread 对象的锁定本身并且由于您的 run() 方法也在同一个 Thread 对象上同步,因此您会看到这种意想不到的情况。

于 2011-08-30T16:00:26.287 回答
3

Thread.join 的实现使用了等待,它释放了它的锁,这就是它不阻止其他线程获取锁的原因。

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

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

然后新线程可以进入等待方法并释放它的锁。此时新线程进入休眠状态,它不会再次尝试获取锁,直到它被唤醒。线程尚未从等待方法返回。

此时主线程从睡眠中唤醒,并在 MyThread 对象上调用 shutdown。获取锁没有问题,因为新线程一旦开始等待就会释放它。主线程现在调用通知。进入join方法,主线程检查新线程是否还活着,然后等待,释放锁。

一旦主线程释放锁,通知就会发生。由于新线程在主线程调用通知时处于等待锁定的状态,因此新线程接收到通知并唤醒。它可以获取锁,离开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;
    }
}
}
于 2011-08-30T15:58:26.893 回答
0

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

从中学习:

  • 不要子类化Thread,而是使用Runnable传递给你的线程对象的实现。
  • 不要同步/等待/通知您不“拥有”的对象,例如,您不知道还有谁可以同步/等待/通知它。
于 2011-08-30T16:07:31.363 回答