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

发布于 2024-12-01 21:41:33 字数 1536 浏览 1 评论 0原文

给出以下 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 的同时不会访问 MustShutdown。然后它通过notify()唤醒另一个线程并通过join()等待其完成。

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

Given the following Java code:

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()");
        }
    }
}

Running this will wait for one second and then properly exit. But that is unexpected to me, I expect a dead-lock to happen here.

My reasoning is as follows: The newly created MyThread will execute run(), which is declared as 'synchronized', so that it may call wait() and safely read 'mustShutdown'; during that wait() call, the lock is released and re-acquired upon returning, as described in the documentation of wait(). After one second, the main thread executes shutdown(), which is again synchronized as to not access mustShutdown at the same time as it's being read by the other thread. It then wakes up the other thread via notify() and the waits for its completion via join().

But in my opinion, there's no way that the other thread can ever return from wait(), since it needs to re-acquire the lock on the thread object before returning. It cannot do so because shutdown() still holds the lock while inside join(). Why does it still work and exit properly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

标点 2024-12-08 21:41:33

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 对象 本身上,从而放弃对 Thread 对象本身的锁定,并且由于您的 run() 方法也在同一个 Thread 对象上同步,因此您会看到这种意想不到的情况。

join() method internally calls wait() which will result in releasing of the lock(of Thread object).

See the code of join() below:

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

Reason why your code sees this and not seen in general:: The reason why your code see this and not is not observed in general, is because the join() method waits() on Thread object itself and consequently relinquishes lock on the Thread object itself and as your run() method also synchronizes on the same Thread object, you see this otherwise unexpected scenario.

椒妓 2024-12-08 21:41:33

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

下面是对该示例中所发生情况的逐步描述:

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

然后新线程可以进入 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;
    }
}
}

The implementation of Thread.join uses wait, which lets go of its lock, which is why it doesn't prevent the other thread from acquiring the lock.

Here is a step-by-step description of what happens in this example:

Starting the MyThread thread in the main method results in a new thread executing the MyThread run method. The main Thread sleeps for a whole second, giving the new Thread plenty of time to start up and acquire the lock on the MyThread object.

The new thread can then enter the wait method and release its lock. At this point the new thread goes dormant, it won't try to acquire the lock again until it is woken up. The thread does not return from the wait method yet.

At this point the main thread wakes up from sleeping and calls shutdown on the MyThread object. It has no problem acquiring the lock because the new thread released it once it started waiting. The main thread calls notify now. Entering the join method, the main thread checks that the new thread is still alive, then waits, releasing the lock.

The notification happens once the main thread releases the lock. Since the new thread was in the wait set for the lock at the time the main thread called notify, the new thread receives the notification and wakes up. It can acquire the lock, leave the wait method, and finish executing the run method, finally releasing the lock.

The termination of the new thread causes all threads waiting on its lock to receive a notification. This wakes up the main thread, it can acquire the lock and check that the new thread is dead, then it will exit the join method and finish executing.

/**
 * 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;
    }
}
}
云巢 2024-12-08 21:41:33

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

从中吸取教训:

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

To complement the other answers: I see no mention of join() releasing any locks in the API-documentation, so this behavior is actually implementation-specific.

Learn from this:

  • don't subclass Thread, instead use a Runnable implementation passed to your thread object.
  • don't synchronize/wait/notify on objects you don't "own", e.g. where you don't know who else might synchronize/wait/notify on it.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文