为什么我应该使用notify?

3

我正在阅读我的SCJP书籍,目前在关于线程的章节上。我想知道为什么要使用notify。

这里是一个使用notify的示例程序:

class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();
            }catch(InterruptedException e) {}
            System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;
    public void run() {
        synchronized(this) {
            for(int i = 0; i < 100000000; i++){
                total++;
            }
            notify();
        }       
        for(int i = 0; i < 100000000; i++){
            total++;
        }
    }
}

如果我去掉 notify 调用,它仍然以相同的方式执行。即,一旦释放锁定,b.wait() 最终会停止阻塞,我们将得到一个在100000000至200000000之间的半随机数,具体取决于调度程序。
此代码:
class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();
            }catch(InterruptedException e) {}
            System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;
    public void run() {
        synchronized(this) {
            for(int i = 0; i < 100000000; i++){
                total++;
            }
            notify();
        }       
    }
}

无论是否存在通知,始终打印100000000。

而这段代码:

class ThreadA {
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();

        synchronized(b){
            try{
                System.out.println("Waiting for b to complete...");
                b.wait();
            }catch(InterruptedException e) {}
            System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;
    public void run() {
        synchronized(this) {
            for(int i = 0; i < 100000000; i++){
                total++;
            }
            notify();
            for(int i = 0; i < 100000000; i++){
                total++;
            }
        }       
    }
}

无论是否存在通知,始终打印出200000000。

据我所知,通知的唯一作用可能是比需要更早地唤醒线程,如果是这样,为什么要使用通知呢?为什么不等待锁释放并让JVM重新启动另一个线程呢?

1个回答

3
除非您或其他人调用了notify(),否则wait()应该一直持续下去,除非有虚假唤醒的情况。您确定没有任何东西中断了线程吗?
基本上,您精确地使用notify()来唤醒线程。如果您不需要将线程休眠,直到另一个线程可以通知它应该唤醒为止,请不要使用它。
编辑:好的,我已经重现了这种行为-我怀疑这是因为您在线程对象本身上调用了wait()。我猜想当线程终止时,Thread对象会得到通知。
尝试等待共享的普通Object,例如:
class ThreadA {

    static Object monitor = new Object();

    public static void main(String[] args) {
        ThreadB b = new ThreadB();
        b.start();

        synchronized(monitor) {
            try {
                System.out.println("Waiting for b to complete...");
                monitor.wait();
            }catch(InterruptedException e) {}
            System.out.println("Total is: " + b.total);
        }
    }
}

class ThreadB extends Thread {
    int total;
    public void run() {

        synchronized(ThreadA.monitor) {
            for (int i = 0; i < 100000000; i++) {
                total++;
            }
//            ThreadA.monitor.notify();
        }       
    }
}

如果您取消注释该行,则程序将终止,否则不会。
编辑:我实际上在这里找到了一些文档。来自Thread.join(millis, nanos)
随着线程终止,将调用this.notifyAll方法。建议应用程序不要在Thread实例上使用wait、notify或notifyAll。

@Malfist:绝对正确。我相信这只是因为你恰好在等待Thread对象本身。 - Jon Skeet
@Malfist:我猜是Thread代码的某个部分在线程退出时调用了notify()。这只是另一个不要在任意对象上加锁或等待的原因 - 只在完全控制的对象上进行操作。 - Jon Skeet
@Malfist:当然,它可能是未记录的 - 甚至可能是JVM特定的。这难道不更鼓励你*不要在Thread对象上wait()*吗? - Jon Skeet
@Malfist:已找到一些文档,证实了通知,并重申我的建议不要在Thread对象上调用wait(等待)等方法。请查看我编辑的答案底部。 - Jon Skeet
@Jon,是的,Java 线程在自身上同步并在退出时调用“notify()”。半年前,我发表了一篇文章,阐述为什么应该避免在“Thread”上进行同步。这会使其在自身上进行大量同步(部分原因是为了遵循规范)。 - bestsss
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接