Java多線程 -- wait() 和 notify() 使用入門

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

所屬對象

wait,notify,notifyAll 是定義在Object類的實例方法,用於控制線程狀態。api

文檔分析

咱們找到Object類,下載它的文檔,翻譯每一個方法的註釋。bash

總結以下:jvm

  1. wait() 和 notify() 必須由對象持有者去調用,有三種方式: 1️⃣執行該對象的synchronized實例方法 2️⃣執行synchronized代碼塊 3️⃣執行該類的synchronized靜態方法ide

  2. 當想要調用wait( )進行線程等待時,必需要取得這個鎖對象的控制權(對象監視器),通常是放到synchronized(obj)代碼中。性能

  3. 在while循環裏用wait操做性能更好(比if判斷)測試

  4. 調用obj.wait( )釋放了obj的鎖,不然其餘線程也沒法得到obj的鎖,也就沒法在synchronized(obj){ obj.notify() } 代碼段內喚醒A。ui

  5. notify( )方法只會通知等待隊列中的第一個相關線程(不會通知優先級比較高的線程)this

  6. notifyAll( )通知全部等待該競爭資源的線程(也不會按照線程的優先級來執行)spa

  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的同步鎖,執行完該方法後纔會釋放鎖。

相關文章
相關標籤/搜索