通知()不使用Wait()

时间:2014-06-19 20:07:29

标签: java multithreading wait notify

我是线程和学习的新手但在下面的代码中为什么通知不起作用。根据我的理解,notify应执行主线程并在i = 5时打印总数。如果我错了,请纠正。

public 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){
            e.printStackTrace();
        }

        System.out.println("Total is: " + b.total);
    }
}
}

class ThreadB extends Thread{
    int total;
    @Override
    public void run(){
        synchronized(this){
            for(int i=0; i<10 ; i++){
                total += i;
             if(i==5){ 
                 System.out.println("Notify");
                 notify();
                 }
                System.out.println("Total is in cal thread: "+i+":" + total);
            }

        }
    }
}

以下是该计划的输出:

Waiting for b to complete... 
Total is in cal thread: 0:0 
Total is in cal thread: 1:1 
Total is in cal thread: 2:3 
Total is in cal thread: 3:6 
Total is in cal thread: 4:10 
Notify 
Total is in cal thread: 5:15 
Total is in cal thread: 6:21 
Total is in cal thread: 7:28 
Total is in cal thread: 8:36 
Total is in cal thread: 9:45 
Total is: 45 – 

3 个答案:

答案 0 :(得分:3)

synchronized(b){
    try{
        System.out.println("Waiting for b to complete...");
        b.wait();

调用notify仅影响当前正在等待的线程,在调用后开始等待的线程不会受到影响。请勿拨打wait,直到您可以在同步区块内100%确认您等待的事情已经发生。

答案 1 :(得分:2)

当你说:

  

根据我的理解,notify应该执行主线程并在i = 5时打印总数。

这不是如何运作的;在您将调用通知的synchronized块中的哪个位置无关紧要,直到通知线程释放锁定才会发生通知。 ThreadB在主线程收到通知之前完成执行for循环。接收通知的线程无论如何都不能在此之前动作,它需要在它离开wait方法之前获取锁。这就是为什么所有“Total in cal”行都会在“Total is”行之前打印出来。

如果您希望早先显示“Total is”,那么您必须更改ThreadB以更快地释放其锁定。您可以将同步移动到for循环内,以便在每次递增后释放锁定。

此处的通知调用实际上是多余的,您可以将其注释掉,程序仍然有效。

这里发生的是,因为你正在使用线程b作为锁,当该线程终止it performs a notifyAll时,任何线程都会等待获取它。这在api doc for Thread#join

中有所描述
  

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

如果您运行此版本的程序,我更改为使用不是线程的其他锁,您可以看到没有这样的隐式通知,以及程序调用notify是否真的很重要。在此版本中,注释掉线程b完成的通知将导致程序挂起。

public class ThreadA {      
    public static final Object lock = new Object();     
    public static void main(String[] args){
        ThreadB b = new ThreadB();
        b.start();
        synchronized(lock){
            try{
                System.out.println("Waiting for b to complete...");
                lock.wait();
            }catch(InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("Total is: " + b.total);
        }
        System.out.println("done");
    }
}

class ThreadB extends Thread{
    int total;
    @Override
    public void run(){
        synchronized(ThreadA.lock){
            for(int i=0; i<10 ; i++){
                total += i;
                if(i==5){ 
                    System.out.println("Notify");
                    ThreadA.lock.notify(); // hangs if you comment this out
                }
                System.out.println("Total is in cal thread: "+i+":" + total);
            }
        }
    }
}

作为一项规则,您应该在循环内部调用wait,如下所示:

synchronized(b) {
    try{
        // b.total needs to be volatile for this to work
        while (b.total < 5) {
            System.out.println("Waiting for b to complete...");
            b.wait();
        }
    }catch(InterruptedException e){
        e.printStackTrace();
    }

这将消除顺序依赖,您假设在通知之前调用wait。这不会在您的示例中显示为问题,因为

a)主线程有一个巨大的开端(threadB必须启动并被调度程序选择运行,而主线程已经执行)和

b)notifyAll由被锁定的终止线程发送。

在现实世界中,在循环中包装wait肯定是一个好主意,用于删除顺序依赖性,用于处理另一个线程进入并在通知线程的时间和时间之间发生变化的情况。它可以重新获取锁定,并处理spurious wakeups(由于竞争条件,线程可以在没有得到通知的情况下被唤醒)。

答案 2 :(得分:1)

如果是多线程,您在b.wait()中调用main之前不确定notify()线程中的ThreadB是否会被调用。有些时候调用b.start()时,run ThreadB方法会在执行main线程的其余语句之前开始执行。

  

如果在wait之后调用nofity,则main线程将等待无限时间,在这种情况下,没有人再次通知它。


如果你想在另一个线程完成所有活动后最终完成main线程,那么你可以使用等待该线程死亡的Thread.join()。不需要waitnotifysynchronized

示例代码。

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

        try {
            b.join();
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }

        System.out.println("Waiting for b to complete...");
        System.out.println("Total is: " + b.total);
    }
}

class ThreadB extends Thread {
    int total;

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            total += i;
        }

    }
}

如果您想详细了解 Concurrency ,请在此处找到 Guarded Blocks ,通过创建 来解释它生产者 - 消费者 应用程序。