1. 程式人生 > >Java並發之線程間協作Object的wait()、notify()、notifyAll()

Java並發之線程間協作Object的wait()、notify()、notifyAll()

它的 ring sleep方法 子類 string exce 程序退出 data- 差異

wait()、notify()和notifyAll()是Object類中的方法:

1)wait()、notify()和notifyAll()方法是本地方法,而且為final方法,無法被重寫。 

2)調用某個對象的wait()方法能讓當前線程堵塞。而且當前線程必須擁有此對象的monitor(即鎖)

3)調用某個對象的notify()方法可以喚醒一個正在等待這個對象的monitor的線程,假設有多個線程都在等待這個對象的 monitor。則僅僅能喚醒當中一個線程;

4)調用notifyAll()方法可以喚醒全部正在等待這個對象的monitor的線程;

為何這三個不是Thread類聲明中的方法。而是Object類中聲明的方法(當然因為Thread類繼承了Object類,所以Thread也能夠調用者三個方法)?事實上這個問題非常easy。因為每一個對象都擁有monitor(即鎖),所以讓當前線程等待某個對象的鎖。當然應該通過這個對象來操作了。而不是用當前線程來操作,因為當前線程可能會等待多個線程的鎖。假設通過線程來操作。就非常復雜了。

上面已經提到,假設調用某個對象的wait()方法,當前線程必須擁有這個對象的monitor(即鎖),因此調用wait()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)
調用某個對象的wait()方法,相當於讓當前線程交出此對象的monitor,然後進入等待狀態,等待興許再次獲得此對象的鎖(Thread類中的sleep方法使當前線程暫停運行一段時間。從而讓其它線程有機會繼續運行。但它並不釋放對象鎖);
notify()方法可以喚醒一個正在等待該對象的monitor的線程,當有多個線程都在等待該對象的monitor的話。則僅僅能喚醒當中一個線程。詳細喚醒哪個線程則不得而知。


相同地,調用某個對象的notify()方法,當前線程也必須擁有這個對象的monitor。因此調用notify()方法必須在同步塊或者同步方法中進行(synchronized塊或者synchronized方法)。
nofityAll()方法可以喚醒全部正在等待該對象的monitor的線程,這一點與notify()方法是不同的。
這裏要註意一點:notify()和notifyAll()方法僅僅是喚醒等待該對象的monitor的線程。並不決定哪個線程可以獲取到monitor。
舉個簡單的樣例:假如有三個線程Thread1、Thread2和Thread3都在等待對象objectA的monitor,此時Thread4擁有對象objectA的monitor,當在Thread4中調用objectA.notify()方法之後,Thread1、Thread2和Thread3僅僅有一個能被喚醒。

註意。被喚醒不等於立馬就獲取了objectA的monitor。假若在Thread4中調用objectA.notifyAll()方法,則Thread1、Thread2和Thread3三個線程都會被喚醒,至於哪個線程接下來可以獲取到objectA的monitor就詳細依賴於操作系統的調度了。
上面尤其要註意一點。一個線程被喚醒不代表馬上獲取了對象的monitor。僅僅有等調用完notify()或者notifyAll()並退出synchronized塊,釋放對象鎖後,其余線程才可獲得鎖運行。


package com.gpl.concurrent.lock;
public class Test {
    public static Object object = new Object();
    public static void main(String[] args) {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.setName("我是Thread1");
        thread2.setName("我是Thread2");
        thread1.start();
         
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
         
        thread2.start();
    }
     
    static class Thread1 extends Thread{
        @Override
        public void run() {
            synchronized (object) {
                try {
                	String name=Thread.currentThread().getName();
                	System.out.println(name+"運行了");
                    object.wait();
                    System.out.println(name+"繼續運行");
                } catch (InterruptedException e) {
                }
                System.out.println("線程"+Thread.currentThread().getName()+"獲取到了鎖");
            }
        }
    }
     
    static class Thread2 extends Thread{
        @Override
        public void run() {
            synchronized (object) {
            	String name=Thread.currentThread().getName();
            	System.out.println(name+"運行了");
                object.notify();
                System.out.println("線程"+Thread.currentThread().getName()+"調用了object.notify()");
            }
            System.out.println("線程"+Thread.currentThread().getName()+"釋放了鎖");
        }
    }
}
結果:

技術分享技術分享


在Java中,能夠通過配合調用Object對象的wait()方法和notify()方法或notifyAll()方法來實現線程間的通信。在線程中調用wait()方法。將堵塞等待其它線程的通知(其它線程調用notify()方法或notifyAll()方法),在線程中調用notify()方法或notifyAll()方法,將通知其它線程從wait()方法處返回。
Object是全部類的超類,它有5個方法組成了等待/通知機制的核心:notify()、notifyAll()、wait()、wait(long)和wait(long,int)。

在Java中,全部的類都從Object繼承而來。因此,全部的類都擁有這些共同擁有方法可供使用。並且。因為他們都被聲明為final。因此在子類中不能覆寫不論什麽一個方法。


這裏具體說明一下各個方法在使用中須要註意的幾點:
1、wait()
public final void wait() throws InterruptedException,IllegalMonitorStateException
該方法用來將當前線程置入休眠狀態,直到接到通知或被中斷為止。在調用wait()之前。線程必需要獲得該對象的對象級別鎖,即僅僅能在同步方法或同步塊中調用wait()方法。

進入wait()方法後。當前線程釋放鎖。在從wait()返回前,線程與其它線程競爭又一次獲得鎖。假設調用wait()時,沒有持有適當的鎖,則拋出IllegalMonitorStateException。它是RuntimeException的一個子類。因此,不需要try-catch結構。
2、notify()
public final native void notify() throws IllegalMonitorStateException
該方法也要在同步方法或同步塊中調用,即在調用前。線程也必需要獲得該對象的對象級別鎖。的假設調用notify()時沒有持有適當的鎖,也會拋出IllegalMonitorStateException。
該方法用來通知那些可能等待該對象的對象鎖的其它線程。假設有多個線程等待,則線程規劃器隨意挑選出當中一個wait()狀態的線程來發出通知。並使它等待獲取該對象的對象鎖(notify後,當前線程不會立即釋放該對象鎖。wait所在的線程並不能立即獲取該對象鎖。要等到程序退出synchronized代碼塊後,當前線程才會釋放鎖,wait所在的線程也才幹夠獲取該對象鎖),但不驚動其它相同在等待被該對象notify的線程們。當第一個獲得了該對象鎖的wait線程執行完成以後,它會釋放掉該對象鎖,此時假設該對象沒有再次使用notify語句,則即便該對象已經空暇。其它wait狀態等待的線程因為沒有得到該對象的通知,會繼續堵塞在wait狀態。直到這個對象發出一個notify或notifyAll。

這裏須要註意:它們等待的是被notify或notifyAll,而不是鎖。這與以下的notifyAll()方法執行後的情況不同。
3、notifyAll()
public final native void notifyAll() throws IllegalMonitorStateException
該方法與notify()方法的工作方式同樣。重要的一點差異是:
notifyAll使全部原來在該對象上wait的線程統統退出wait的狀態(即全部被喚醒。不再等待notify或notifyAll,但因為此時還沒有獲取到該對象鎖,因此還不能繼續往下運行)。變成等待獲取該對象上的鎖。一旦該對象鎖被釋放(notifyAll線程退出調用了notifyAll的synchronized代碼塊的時候)。他們就會去競爭。假設當中一個線程獲得了該對象鎖,它就會繼續往下運行,在它退出synchronized代碼塊。釋放鎖後,其它的已經被喚醒的線程將會繼續競爭獲取該鎖。一直進行下去。直到全部被喚醒的線程都運行完成。
4、wait(long)和wait(long,int)
顯然。這兩個方法是設置等待超時時間的,後者在超值時間上加上ns。精度也難以達到。因此,該方法非常少使用。對於前者。假設在等待線程接到通知或被中斷之前,已經超過了指定的毫秒數,則它通過競爭又一次獲得鎖。並從wait(long)返回。另外。須要知道,假設設置了超時時間。當wait()返回時。我們不能確定它是由於接到了通知還是由於超時而返回的,由於wait()方法不會返回不論什麽相關的信息。

但一般能夠通過設置標誌位來推斷,在notify之前改變標誌位的值。在wait()方法後讀取該標誌位的值來推斷,當然為了保證notify不被遺漏,我們還須要另外一個標誌位來循環推斷是否調用wait()方法。
深入理解:
假設線程調用了對象的wait()方法。那麽線程便會處於該對象的等待池中。等待池中的線程不會去競爭該對象的鎖。
當有線程調用了對象的notifyAll()方法(喚醒全部wait線程)或notify()方法(僅僅隨機喚醒一個wait線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。


優先級高的線程競爭到對象鎖的概率大,假若某線程沒有競爭到該對象鎖,它還會留在鎖池中,唯有線程再次調用wait()方法。它才會又一次回到等待池中。而競爭到對象鎖的線程則繼續往下運行。直到運行完了synchronized代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。


參考:http://www.cnblogs.com/dolphin0520/p/3920385.html

Java並發之線程間協作Object的wait()、notify()、notifyAll()