Java 多線程編程之:notify 和 wait 用法

最近看帖子,發現一道面試題:java

啓動兩個線程, 一個輸出 1,3,5,7…99, 另外一個輸出 2,4,6,8…100 最後 STDOUT 中按序輸出 1,2,3,4,5…100

題目要求用 Java 的 wait + notify 機制來實現,重點考察對於多線程可見性的理解。面試

wait 和 notify 簡介

wait 和 notify 均爲 Object 的方法:多線程

  • Object.wait() —— 暫停一個線程
  • Object.notify() —— 喚醒一個線程

從以上的定義中,咱們能夠了解到如下事實:ide

  • 想要使用這兩個方法,咱們須要先有一個對象 Object。
  • 在多個線程之間,咱們能夠經過調用同一個對象wait()notify()來實現不一樣的線程間的可見。

對象控制權(monitor)

在使用 wait 和 notify 以前,咱們須要先了解對象的控制權(monitor)。在 Java 中任何一個時刻,對象的控制權只能被一個線程擁有。如何理解控制權呢?請先看下面的簡單代碼:this

public class ThreadTest {
    public static void main(String[] args) {
        Object object = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

直接執行,咱們將會獲得如下異常:線程

Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.xiangyu.demo.ThreadTest$1.run(ThreadTest.java:10)
    at java.lang.Thread.run(Thread.java:748)

出錯的代碼在:object.wait();。這裏咱們須要瞭解如下事實:code

  • 不管是執行對象的 wait、notify 仍是 notifyAll 方法,必須保證當前運行的線程取得了該對象的控制權(monitor)
  • 若是在沒有控制權的線程裏執行對象的以上三種方法,就會報 java.lang.IllegalMonitorStateException 異常。
  • JVM 基於多線程,默認狀況下不能保證運行時線程的時序性

在上面的示例代碼中,咱們 new 了一個 Thread,可是對象 object 的控制權仍在主線程裏。因此會報 java.lang.IllegalMonitorStateException 。對象

咱們能夠經過同步鎖來得到對象控制權,例如:synchronized 代碼塊。對以上的示例代碼作改造:同步

public class ThreadTest {
    public static void main(String[] args) {
        Object object = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object){ // 修改處
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

再次執行,代碼再也不報錯。it

咱們能夠獲得如下結論:

  • 調用對象的wait()notify()方法,須要先取得對象的控制權
  • 可使用synchronized (object)來取得對於 object 對象的控制權

解題

瞭解了對象控制權以後,咱們就能夠正常地使用 notify 和 wait 了,下面給出個人解題方法,供參考。

public class ThreadTest {
    private final Object flag = new Object();

    public static void main(String[] args) {
        ThreadTest threadTest = new ThreadTest();
        ThreadA threadA = threadTest.new ThreadA();
        threadA.start();
        ThreadB threadB = threadTest.new ThreadB();
        threadB.start();
    }

    class ThreadA extends Thread {
        @Override
        public void run() {
            synchronized (flag) {
                for (int i = 0; i <= 100; i += 2) {
                    flag.notify();
                    System.out.println(i);
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        }
    }

    class ThreadB extends Thread {
        @Override
        public void run() {
            synchronized (flag) {
                for (int i = 1; i < 100; i += 2) {
                    flag.notify();
                    System.out.println(i);
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

發散:notify()notifyAll()

這兩個方法均爲 native 方法,在JDK 1.8 中的關於notify()的JavaDoc以下:

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened.

譯爲:

喚醒此 object 控制權下的一個處於 wait 狀態的線程。如有多個線程處於此 object 控制權下的 wait 狀態,只有一個會被喚醒。

也就是說,若是有多個線程在 wait 狀態,咱們並不知道哪一個線程會被喚醒。

在JDK 1.8 中的關於notifyAll()的JavaDoc以下:

Wakes up all threads that are waiting on this object's monitor.

譯爲:

喚醒全部處於此 object 控制權下的 wait 狀態的線程。

因此,咱們須要根據實際的業務場景來考慮如何使用。

相關文章
相關標籤/搜索