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

gsi*_*011 4 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Ser*_*mme 11

你误解了如何wait/ notify有效.wait不能阻止在其上调用它的螺纹; 它阻塞当前线程,直到在同一个对象上调用notify (所以如果你有线程A和B,而在线程A中,称为B.wait(),这将停止线程A而不是线程B - 只要B.notify()未被调用).

因此,在您的具体示例中,如果您希望首先执行主线程,则需要将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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,即使此代码可能无法正常工作.在主线程有可能到达wait ()部分之前到达notify()部分的情况下(在你的情况下不太可能,但仍然可能 - 你可以观察它,如果你把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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  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. 整个应用程序终止.

正如您所看到的,即使在这样一个看似简单的场景中,也会发生很多事情.