让线程暂停 - Thread.wait()/ Thread.notify()

时间:2012-01-23 23:44:41

标签: java multithreading synchronization

我正在尝试理解线程是如何工作的,我写了一个简单的例子,我想创建并启动一个新线程,线程,在主线程中显示从1到1000的数字,恢复辅助线程,并在辅助线程中显示1到1000之间的数字。当我省略Thread.wait()/ Thread.notify()时,它的行为与预期一致,两个线程一次显示几个数字。当我添加这些函数时,由于某种原因,主线程的数字是第二个而不是第一个打印的。我究竟做错了什么?

public class Main {

    public class ExampleThread extends Thread {

        public ExampleThread() {
            System.out.println("ExampleThread's name is: " + this.getName());
        }

        @Override
        public void run() {         
            for(int i = 1; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName());
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        new Main().go();
    }

    public void go() {
        Thread t = new ExampleThread();
        t.start();

        synchronized(t) {
            try {
                t.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        for(int i = 1; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
            System.out.println(i);
        }

        synchronized(t) {
            t.notify();
        }
    }
}

3 个答案:

答案 0 :(得分:11)

您误解了wait / notify的工作原理。 wait 阻止调用它的线程;它会阻止当前线程,直到在同一个对象上调用notify (所以如果你有线程A和B,而在线程A中调用了B.wait(),只要没有调用B.notify(),这将停止线程A和线程B - 。

因此,在您的具体示例中,如果您希望首先执行主线程,则需要将wait()放在辅助线程中。像这样:

public class Main {

    public class ExampleThread extends Thread {

        public ExampleThread() {
            System.out.println("ExampleThread's name is: " + this.getName());
        }

        @Override
        public void run() {         
            synchronized (this) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
            for(int i = 1; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName());
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        new Main().go();
    }

    public void go() {
        Thread t = new ExampleThread();
        t.start();

        for(int i = 1; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
            System.out.println(i);
        }

        synchronized(t) {
            t.notify();
        }
    }
}

但是,即使此代码可能无法正常工作。在主线程到达之前的notify()部分的情况下,辅助线程有机会到达wait()部分(在你的情况下不太可能,但仍然可能 - 你可以观察它)如果你把Thread.sleep放在辅助线程的开头),那么辅助线程永远不会被唤醒。因此,最安全的方法与此类似:

public class Main {

    public class ExampleThread extends Thread {

        public ExampleThread() {
            System.out.println("ExampleThread's name is: " + this.getName());
        }

        @Override
        public void run() {
            synchronized (this) {
                try {
                    notify();
                    wait();
                } catch (InterruptedException e) {
                }
            }
            for(int i = 1; i < 1000; i++) {
                System.out.println(Thread.currentThread().getName());
                System.out.println(i);
            }
        }
    }

    public static void main(String[] args) {
        new Main().go();
    }

    public void go() {
        Thread t = new ExampleThread();
        synchronized (t) {
            t.start();
            try {
                t.wait();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        for(int i = 1; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName());
            System.out.println(i);
        }

        synchronized(t) {
            t.notify();
        }
    }
}

在此示例中,输出完全是确定性的。这是发生的事情:

  1. 主线程创建一个新的t对象。
  2. 主线程锁定t监视器。
  3. 主线程启动t主题。
  4. (这些可以按任何顺序发生)
    1. 辅助线程启动,但由于主线程仍然拥有t监视器,因此辅助线程无法继续并且必须等待(因为它的第一个语句是synchronized (this)不是,因为碰巧 t对象 - 所有锁定,通知和等待都可以完全与完全无关的具有相同结果的2个线程中的任何一个完成。
    2. 主线程继续,到达t.wait()部分并暂停执行,释放它同步的t监视器。
  5. 辅助线程获得t监视器的所有权。
  6. 辅助线程调用t.notify(),唤醒主线程。但是主线程还不能继续,因为辅助线程仍然拥有t监视器的所有权。
  7. 辅助线程调用t.wait(),暂停执行并释放t监视器。
  8. 主线程最终可以继续,因为t监视器现在可用。
  9. 主线程获得t监视器的所有权,但立即释放它。
  10. 主线程进行数字计数。
  11. 主线程再次获得t监视器的所有权。
  12. 主线程调用t.notify(),唤醒辅助线程。辅助线程暂时无法继续,因为主线程仍然保留t监视器。
  13. 主线程释放t监视器并终止。
  14. 辅助线程获得t监视器的所有权,但立即释放它。
  15. 辅助线程进行数字计数,然后终止。
  16. 整个申请终止。
  17. 正如你所看到的,即使是在这样一个看似简单的场景中,也会有很多事情发生。

答案 1 :(得分:2)

ExampleThread不会wait()notify(),也不会synchronized。所以它会在没有与其他线程协调的情况下随时运行。

主线程正在等待永远不会发出的通知(此通知由另一个线程发送)。我的猜测是当ExampleThread死亡时,主线程被“虚假地”唤醒并完成。

应该等待另一个完成的线程必须在检查条件的循环内执行对wait()的调用:

class ExampleThread extends Thread {

  private boolean ready = false;

  synchronized void ready() { 
    ready = true; 
    notifyAll();
  }

  @Override
  public void run() {
    /* Wait to for readiness to be signaled. */
    synchronized (this) {
      while (!ready)
        try {
          wait();
        } catch(InterruptedException ex) {
          ex.printStackTrace();
          return; /* Interruption means abort. */
        }
    }
    /* Now do your work. */
    ...

然后在你的主线程中:

ExampleThread t = new ExampleThread();
t.start();
/* Do your work. */
...
/* Then signal the other thread. */
t.ready();

答案 2 :(得分:2)

你很幸运,你的程序终止了。

当您致电t.wait()时,主线程会停止并无限期地等待通知。

永远不会得到它,但我相信当辅助线程完成时,虚假唤醒会唤醒它。 (阅读here关于虚假唤醒的内容。)