1. 程式人生 > >Thread之七:Object裡的wait、notify、notifyAll的使用方法

Thread之七:Object裡的wait、notify、notifyAll的使用方法

wait()、notify()、notifyAll()是三個定義在Object類裡的方法,可以用來控制執行緒的狀態

public final native void notify();

public final native void notifyAll();

public final native void wait(long l)
        throws InterruptedException;

    public final void wait(long l, int i)
        throws InterruptedException
    {
        
if(l < 0L) throw new IllegalArgumentException("timeout value is negative"); if(i < 0 || i > 999999) throw new IllegalArgumentException("nanosecond timeout value out of range"); if(i > 0) l++; wait(l); } public final
void wait() throws InterruptedException { wait(0L); }

這三個方法最終呼叫的都是jvm級的final native方法。隨著jvm執行平臺的不同可能有些許差異。

  • 如果物件呼叫了wait方法就會使持有該物件的執行緒把該物件的控制權交出去,然後處於等待狀態。
  • 如果物件呼叫了notify方法就會通知某個正在等待這個物件的控制權的執行緒可以繼續執行。
  • 如果物件呼叫了notifyAll方法就會通知所有等待這個物件控制權的執行緒繼續執行。

其中wait方法有三個over load方法:

wait()

wait(long)

wait(long,int)

wait方法通過引數可以指定等待的時長。如果沒有指定引數,預設一直等待直到被通知。

以下是一個演示程式碼,以最簡潔的方式說明覆雜的問題:

簡要說明下:

NotifyThread是用來模擬3秒鐘後通知其他等待狀態的執行緒的執行緒類;

WaitThread是用來模擬等待的執行緒類;

等待的中間物件是flag,一個String物件;

main方法中同時啟動一個Notify執行緒和三個wait執行緒;

package com.dxz.synchronizeddemo;

public class NotifyTest {
    private String flag = "true";

    class NotifyThread extends Thread {
        public NotifyThread(String name) {
            super(name);
        }

        public void run() {
            try {
                sleep(3000);// 推遲3秒鐘通知
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            flag = "false";
            flag.notify();
        }
    };

    class WaitThread extends Thread {
        public WaitThread(String name) {
            super(name);
        }

        public void run() {
            System.out.println(getName() +  "  flag:" + flag);
            while (!flag.equals("false")) {
                System.out.println(getName() + " begin waiting!");
                long waitTime = System.currentTimeMillis();
                try {
                    flag.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                waitTime = System.currentTimeMillis() - waitTime;
                System.out.println("wait time :" + waitTime);
            }
            System.out.println(getName() + " end waiting!");

        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Main Thread Run!");
        NotifyTest test = new NotifyTest();
        NotifyThread notifyThread = test.new NotifyThread("notify01");
        WaitThread waitThread01 = test.new WaitThread("waiter01");
        WaitThread waitThread02 = test.new WaitThread("waiter02");
        WaitThread waitThread03 = test.new WaitThread("waiter03");
        notifyThread.start();
        waitThread01.start();
        waitThread02.start();
        waitThread03.start();
    }
}

結果:

Main Thread Run!
Exception in thread "waiter03" waiter03  flag:true
waiter02  flag:true
waiter03 begin waiting!
waiter01  flag:true
waiter02 begin waiting!
waiter01 begin waiting!
Exception in thread "waiter02" Exception in thread "waiter01" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.dxz.synchronizeddemo.NotifyTest$WaitThread.run(NotifyTest.java:34)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.dxz.synchronizeddemo.NotifyTest$WaitThread.run(NotifyTest.java:34)
java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at java.lang.Object.wait(Object.java:502)
    at com.dxz.synchronizeddemo.NotifyTest$WaitThread.run(NotifyTest.java:34)
Exception in thread "notify01" java.lang.IllegalMonitorStateException
    at java.lang.Object.notify(Native Method)
    at com.dxz.synchronizeddemo.NotifyTest$NotifyThread.run(NotifyTest.java:19)

在wait和notify的地方都報錯java.lang.IllegalMonitorStateException,前面也講過,wait和notify方法一定要在synchronized裡面,更具體點說有:

  1. 任何一個時刻,物件的控制權(monitor)只能被一個執行緒擁有。
  2. 無論是執行物件的wait、notify還是notifyAll方法,必須保證當前執行的執行緒取得了該物件的控制權(monitor)
  3. 如果在沒有控制權的執行緒裡執行物件的以上三種方法,就會報java.lang.IllegalMonitorStateException異常。
  4. JVM基於多執行緒,預設情況下不能保證執行時執行緒的時序性

基於以上幾點事實,我們需要確保讓執行緒擁有物件的控制權。

也就是說在waitThread中執行wait方法時,要保證waitThread對flag有控制權;

在notifyThread中執行notify方法時,要保證notifyThread對flag有控制權。

  1. 同步的例項方法(鎖用的是其例項物件本身。所有的非靜態同步方法執行需要順序執行,即不能並行執行。)
  2. 同步的靜態方法(鎖用的是其類物件本身。所有的靜態同步方法執行需要順序執行,即不能並行執行。)
  3. 例項方法中的同步塊(鎖是自己指定的,但不能是引用性物件及null物件)
  4. 靜態方法中的同步塊(鎖是自己指定的,但不能是引用性物件及null物件)
我們用第三種方法來做說明: 將以上notify和wait方法包在同步塊中
//nofity放入synchronized中
            synchronized (flag) {
                flag = "false";
                flag.notify();
            }
//wait放入synchronized中
            synchronized (flag) {
                while (!flag.equals("false")) {
                    System.out.println(getName() + " begin waiting!");
                    long waitTime = System.currentTimeMillis();
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    waitTime = System.currentTimeMillis() - waitTime;
                    System.out.println("wait time :" + waitTime);
                }
            }

再執行的結果:

Main Thread Run!
waiter02 flag:true
waiter01 flag:true
waiter03 flag:true
waiter02 begin waiting!
waiter03 begin waiting!
waiter01 begin waiting!
Exception in thread "notify01" java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.dxz.synchronizeddemo.NotifyTest$NotifyThread.run(NotifyTest.java:20)

在flag.notify();的地方還是會報錯java.lang.IllegalMonitorStateException。這時的異常是由於在針對flag物件同步塊中,更改了flag物件的狀態所導致的。如下:

flag="false";  //改變的物件的內容
flag.notify();

對在同步塊中對flag進行了賦值操作,使得flag引用的物件改變這時候再呼叫notify方法時,因為沒有控制權所以丟擲異常

我們可以改進一下,將flag改成一個JavaBean,然後更改它的屬性不會影響到flag的引用

我們這裡改成陣列來試試,也可以達到同樣的效果:

private String flag[] = {"true"}; 
       synchronized (flag) {  
                flag[0] = "false";
                flag.notify();
            }
             synchronized (flag) { 
                System.out.println(getName() +  "  flag:" + flag);
                while (!flag[0].equals("false")) {
                    System.out.println(getName() + " begin waiting!");
                    long waitTime = System.currentTimeMillis();
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    waitTime = System.currentTimeMillis() - waitTime;
                    System.out.println("wait time :" + waitTime);
                }
                System.out.println(getName() + " end waiting!");
            }

這時候再執行,不再報異常,但是執行緒沒有結束是吧,沒錯,還有執行緒堵塞,處於wait狀態。

原因很簡單,我們有三個wait執行緒,只有一個notify執行緒,notify執行緒執行notify方法的時候,是隨機通知一個正在等待的執行緒,所以,現在應該還有兩個執行緒在waiting。

我們只需要將NotifyThread執行緒類中的flag.notify()方法改成notifyAll()就可以了。notifyAll方法會通知所有正在等待物件控制權的執行緒。

最終完成版如下:

package com.dxz.synchronizeddemo;

public class NotifyTest2 {
    private String flag[] = {"true"};  

    class NotifyThread extends Thread {
        public NotifyThread(String name) {
            super(name);
        }

        public void run() {
            try {
                sleep(3000);// 推遲3秒鐘通知
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (flag) {  
                flag[0] = "false";
                flag.notify();
            }
        }
    };

    class WaitThread extends Thread {
        public WaitThread(String name) {
            super(name);
        }

        public void run() {
            synchronized (flag) { 
                System.out.println(getName() +  "  flag:" + flag);
                while (!flag[0].equals("false")) {
                    System.out.println(getName() + " begin waiting!");
                    long waitTime = System.currentTimeMillis();
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    waitTime = System.currentTimeMillis() - waitTime;
                    System.out.println("wait time :" + waitTime);
                }
                System.out.println(getName() + " end waiting!");
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Main Thread Run!");
        NotifyTest2 test = new NotifyTest2();
        NotifyThread notifyThread = test.new NotifyThread("notify01");
        WaitThread waitThread01 = test.new WaitThread("waiter01");
        WaitThread waitThread02 = test.new WaitThread("waiter02");
        WaitThread waitThread03 = test.new WaitThread("waiter03");
        notifyThread.start();
        waitThread01.start();
        waitThread02.start();
        waitThread03.start();
    }
}

notify()和notifyAll()的本質區別

notify()和notifyAll()都是Object物件用於通知處在等待該物件的執行緒的方法。兩者的最大區別在於:

notifyAll使所有原來在該物件上等待被notify的所有執行緒統統退出wait的狀態,變成等待該物件上的鎖,一旦該物件被解鎖,他們就會去競爭。
notify則文明得多,它只是選擇一個wait狀態執行緒進行通知,並使它獲得該物件上的鎖,但不驚動其他同樣在等待被該物件notify的執行緒們,當第一個執行緒執行完畢以後釋放物件上的鎖此時如果該物件沒有再次使用notify語句,則即便該物件已經空閒,其他wait狀態等待的執行緒由於沒有得到該物件的通知,繼續處在wait狀態,直到這個物件發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。

下面是一個很好的例子:

package com.dxz.synchronizeddemo;

public class NotifyTest3 {
    private String flag[] = { "true" };

    class NotifyThread extends Thread {
        public NotifyThread(String name) {
            super(name);
        }

        public void run() {
            try {
                sleep(3000);// 推遲3秒鐘通知
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (flag) {
                flag[0] = "false";
                flag.notifyAll();
            }
        }
    };

    class WaitThread extends Thread {
        public WaitThread(String name) {
            super(name);
        }

        public void run() {
            System.out.println(getName() + "  flag:" + flag);
            synchronized (flag) {
                System.out.println(getName() + "  flag:" + flag);
                while (!flag[0].equals("false")) {
                    System.out.println(getName() + " begin waiting!");
                    long waitTime = System.currentTimeMillis();
                    try {
                        flag.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    waitTime = System.currentTimeMillis() - waitTime;
                    System.out.println("wait time :" + waitTime);
                }
                System.out.println(getName() + " end waiting!");
            }
            System.out.println(getName() + " end waiting!");

        }
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("Main Thread Run!");
        NotifyTest3 test = new NotifyTest3();
        NotifyThread notifyThread = test.new NotifyThread("notify01");
        WaitThread waitThread01 = test.new WaitThread("waiter01");
        WaitThread waitThread02 = test.new WaitThread("waiter02");
        WaitThread waitThread03 = test.new WaitThread("waiter03");
        notifyThread.start();
        waitThread01.start();
        waitThread02.start();
        waitThread03.start();
    }
}

結果:

Main Thread Run!
waiter01  flag:[Ljava.lang.String;@584aceca
waiter01  flag:[Ljava.lang.String;@584aceca
waiter02  flag:[Ljava.lang.String;@584aceca
waiter01 begin waiting!
waiter02  flag:[Ljava.lang.String;@584aceca
waiter02 begin waiting!
waiter03  flag:[Ljava.lang.String;@584aceca
waiter03  flag:[Ljava.lang.String;@584aceca
waiter03 begin waiting!
wait time :3000
waiter03 end waiting!
waiter03 end waiting!
wait time :3001
waiter02 end waiting!
waiter02 end waiting!
wait time :3001
waiter01 end waiting!
waiter01 end waiting!