Java wait()/join():为什么这不会死锁?
给定以下Java代码:Java wait()/join():为什么这不会死锁?,java,multithreading,join,wait,notify,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
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()内时仍持有锁。为什么它仍然正常工作并退出?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;
}
}
}
join()方法在内部调用wait(),这将导致释放(线程对象的)锁
请参见下面join()的代码:
代码通常看到而没有看到的原因::代码通常看不到而没有看到的原因是join()方法等待线程对象本身,从而放弃对线程对象本身的锁定,并将其作为您的运行()方法也在同一个线程对象上同步,您会看到这一意外情况。为了补充其他答案:我在API文档中没有提到释放任何锁的
join()
,因此此行为实际上是特定于实现的
从中学习:
- 不要子类化
,而是使用传递给线程对象的线程
实现可运行
- 不要对您不“拥有”的对象进行同步/等待/通知,例如,您不知道还有谁可能对其进行同步/等待/通知
join
在内部使用wait()
,这是您可能没有想到的。例如,MyThread
对象的监视器用于两个目的:同步您自己的运行/关闭周期,以及同步内部线程管理。从Java 7开始,API规范join(long)规定:“此实现使用This.wait调用的循环,条件是This.isAlive。当线程终止时,调用this.notifyAll方法。“。因为涉及等待,所以也涉及锁。Paulo的回答一定是基于Java 6 API,因为Java 7当时刚刚发布。因为这样的副作用,直接扩展线程是不可取的。你应该实现一个Runnable,用线程包装它。
public final synchronized void join(long millis)
throws InterruptedException {
....
if (millis == 0) {
while (isAlive()) {
wait(0); //ends up releasing lock
}
}
....
}