Java多執行緒 -- wait() 和 notify() 使用入門

微笑面對生活發表於2018-06-24

在前面講解synchronize的文章中,有提到wait和notify,大概描述了它的使用,這裡我將根據官方api詳細的教你如何使用。

所屬物件

wait,notify,notifyAll 是定義在Object類的例項方法,用於控制執行緒狀態。

文件分析

我們找到Object類,下載它的文件,翻譯每個方法的註釋。

總結如下:

  1. wait() 和 notify() 必須由物件持有者去呼叫,有三種方式: 1️⃣執行該物件的synchronized例項方法 2️⃣執行synchronized程式碼塊 3️⃣執行該類的synchronized靜態方法

  2. 當想要呼叫wait( )進行執行緒等待時,必須要取得這個鎖物件的控制權(物件監視器),一般是放到synchronized(obj)程式碼中。

  3. 在while迴圈裡用wait操作效能更好(比if判斷)

  4. 呼叫obj.wait( )釋放了obj的鎖,否則其他執行緒也無法獲得obj的鎖,也就無法在synchronized(obj){ obj.notify() } 程式碼段內喚醒A。

  5. notify( )方法只會通知等待佇列中的第一個相關執行緒(不會通知優先順序比較高的執行緒)

  6. notifyAll( )通知所有等待該競爭資源的執行緒(也不會按照執行緒的優先順序來執行)

  7. 如果是synchronized宣告的方法,wait()操作後會施放synchronized鎖,相反notify()觸發後會重拿起synchronized鎖。

  8. 如果當前執行緒不是當前物件所持有,則會報異常IllegalMonitorStateException

例項

1. 通過呼叫物件的wait和notify實現
/** 呼叫物件的 wait 和 notify 例項
 * Created by Fant.J.
 */
public class Demo {
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("修改flag執行緒執行");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                demo.setFlag(true);
                notify();
                System.out.println("修改flag並釋放鎖成功");
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (demo.isFlag() != true){
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("flag為true時執行緒執行");

            }
        }).start();
    }
}



修改flag執行緒執行
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.thread.waitNotify.Demo$2.run(Demo.java:41)
	at java.lang.Thread.run(Thread.java:748)
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at com.thread.waitNotify.Demo$1.run(Demo.java:31)
	at java.lang.Thread.run(Thread.java:748)
複製程式碼

從執行結果可以看出,它報錯IllegalMonitorStateException,我們上面有給出報該異常的原因,是因為沒有沒有獲取到物件的監視器控制權,我們new了兩個執行緒,一個呼叫了wait 一個呼叫了notify,jvm認為wait是一個執行緒下的wait,notify是另一個執行緒下的notify,事實上,我們想實現的是針對Demo物件的鎖的wait和notify,所以,我們需要呼叫Demo物件的wait和notify方法。

修改後的程式碼:

/** 呼叫物件的 wait 和 notify 例項
 * Created by Fant.J.
 */
public class Demo {
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public static void main(String[] args) {
        Demo demo = new Demo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (demo) {
                    System.out.println("修改flag執行緒執行");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    demo.setFlag(true);
                    demo.notify();
                    System.out.println("修改flag並釋放鎖成功");
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (demo) {
                    while (demo.isFlag() != true) {
                        try {
                            demo.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("flag為true時執行緒執行");
                }
            }
        }).start();
    }
}


修改flag執行緒執行
修改flag並釋放鎖成功
flag為true時執行緒執行
複製程式碼

修改了兩處,一處是加了synchronized程式碼塊,一處是新增了wait和notify的呼叫物件。

2. 通過synchronized修飾方法來實現
package com.thread.waitNotify_1;

/** 通過synchronized方法實現 wait notify
 * Created by Fant.J.
 */
public class Demo2 {
    private volatile boolean flag = false;

    public synchronized boolean getFlag() {
        System.out.println(Thread.currentThread().getName()+"開始執行...");
        if (this.flag != true){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"執行結束...");
        return flag;
    }

    public synchronized void setFlag(boolean flag) {
        this.flag = flag;
        notify();
    }

    public static void main(String[] args) {
        Demo2 demo2 = new Demo2();
        Runnable target1 = new Runnable() {
            @Override
            public void run() {
                demo2.getFlag();
            }
        };

        Runnable target2 = new Runnable() {
            @Override
            public void run() {
                demo2.setFlag(true);
            }
        };

        new Thread(target1).start();
        new Thread(target1).start();
        new Thread(target1).start();
        new Thread(target1).start();
    }
}


Thread-0開始執行...
Thread-1開始執行...
Thread-2開始執行...
Thread-3開始執行...
複製程式碼

為什麼四個執行緒都執行了呢?synchronized不是鎖定執行緒了嗎?我在上面8點中也有說明,wait()操作後,會暫時釋放synchronized的同步鎖,等notify()觸發後,又會重拾起該鎖,保證執行緒同步。

然後我們條用target2來釋放一個執行緒:

        new Thread(target1).start();
        new Thread(target1).start();
        new Thread(target1).start();
        new Thread(target1).start();
        new Thread(target2).start();

Thread-0開始執行...
Thread-1開始執行...
Thread-2開始執行...
Thread-3開始執行...
Thread-0執行結束...

複製程式碼

可以看到只釋放了一個執行緒,並且是第一個執行緒,如果有優先順序,他也是釋放第一個執行緒。

如果把notify改成notifyAll。

Thread-0開始執行...
Thread-2開始執行...
Thread-1開始執行...
Thread-3開始執行...
Thread-3執行結束...
Thread-1執行結束...
Thread-2執行結束...
Thread-0執行結束...
複製程式碼

如何證明,每次notify後會拿到synchronized鎖呢,我在執行notify後新增一些時間戳捕獲幫助我們檢視

    public synchronized void setFlag(boolean flag) {
        this.flag = flag;
//        notify();
        notifyAll();
        System.out.println("測試notify觸發後會不會等2s"+System.currentTimeMillis());
        try {
            Thread.sleep(2000);
            System.out.println("測試notify觸發後會不會等2s"+System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


Thread-0開始執行...
Thread-1開始執行...
Thread-2開始執行...
Thread-3開始執行...
測試notify觸發後會不會等2s1529817196847
測試notify觸發後會不會等2s1529817198847
Thread-3執行結束...
Thread-2執行結束...
Thread-1執行結束...
Thread-0執行結束...
複製程式碼

可以看到的確是notify重拾了synchronized的同步鎖,執行完該方法後才會釋放鎖。

相關文章