意外的线程唤醒 [英] Unexpected thread wakeup

查看:117
本文介绍了意外的线程唤醒的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我期望下面的示例中的第二个线程挂起,因为它等待一个没有相应通知的对象.相反,它可能是由于虚假唤醒而进入了println.

I was expecting the second thread in the following example to hang, since it waits on an object with no corresponding notify. Instead, it falls through to the println, presumably due to a spurious wakeup.

public class Spurious {
    public static void main(String[] args) {

        Thread t1 = new Thread() { 
            public void run() { 
                System.out.println("Hey!"); 
            }  
        };
        Thread t2 = new Thread() { 
            public void run() 
            {
                try {
                    synchronized (t1) {
                        t1.wait();
                    }
                } catch (InterruptedException e) {
                    return;
                }
                System.out.println("Done.");
            }
        };
        t1.start();
        t2.start();
    }
}

输出:

Hey!
Done.

另一方面,如果删除了嘿!"从第一个线程返回println,第二个线程确实会挂起.在MacOS和Linux上都会发生这种情况.

On the other hand, if one removes the "Hey!" println from the first thread, the second thread will indeed hang. This happens on both MacOS and Linux.

知道为什么吗?

推荐答案

这不是虚假的唤醒,虚假的唤醒是由JVM中的竞争条件引起的.这是您代码中的竞争条件.

This is not a spurious wakeup, a spurious wakeup is caused by a race condition in the JVM. This is a race condition in your code.

println使线程1保持活动状态足够长的时间,以使线程2可以在线程1终止之前开始等待.

The println keeps thread1 alive just long enough that thread2 can start waiting before thread1 terminates.

线程1终止后,它将向在其监视器上等待的所有事件发送一条通知.线程2收到通知并停止等待.

Once thread1 terminates it sends a notification to everything waiting on its monitor. thread2 receives the notification and ceases waiting.

删除println减少了线程1完成所需的时间,因此线程1在线程2可以开始等待之前已经完成.线程1不再活动,并且其通知已经在线程2开始等待之前发生,因此线程2将永远等待.

Removing the println reduces the time needed for thread1 to finish so that thread1 has already finished by the time thread2 can start waiting on it. thread1 is no longer alive and its notification has already occurred before thread2 started waiting, so thread2 waits forever.

线程#join的API :

此实现使用this.isAlive为条件的this.wait调用循环.当线程终止时,将调用this.notifyAll方法.建议应用程序不要在线程实例上使用wait,notify或notifyAll.

This implementation uses a loop of this.wait calls conditioned on this.isAlive. As a thread terminates the this.notifyAll method is invoked. It is recommended that applications not use wait, notify, or notifyAll on Thread instances.

(对于要调用notifyAll的线程,它必须持有该锁,如果另一个线程抓住了该锁,则它可以使终止线程保持活动状态并延迟notifyAll,直到终止线程可以获取该锁为止.)

(For a thread to call notifyAll it has to hold the lock, if the other thread grabs the lock, it can keep the terminating thread alive and delay the notifyAll until the terminating thread can acquire the lock.)

道德(嗯,道德之一)是始终在条件变量的循环中等待,请参见

The moral (well, one of the morals) is to always wait in a loop with a condition variable, see the Oracle tutorial. If you change Thread2 to look like this:

    Thread t2 = new Thread() { 
        public void run() 
        {
            try {
                synchronized (t1) {
                    while (t1.isAlive()) {
                        t1.wait();
                    }
                }
            } catch (InterruptedException e) {
                return;
            }
            System.out.println("Done.");
        }
    };

然后,线程2应该退出,而不管线程2是否可以在线程1完成之前开始等待.

then thread2 should exit regardless of whether thread2 can start waiting before thread1 finishes.

这当然是玩具示例的总区域:

Of course this is total toy example territory:

  • 不要扩展Thread,请使用Runnable或Callable.

  • Don't extend Thread, use Runnable or Callable.

不要锁定线程.

不要启动线程,请使用执行程序.

Don't start Threads, use Executors.

建议使用更高级别的并发结构来等待/通知.

Prefer higher level concurrency constructs to wait/notify.

这篇关于意外的线程唤醒的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆