7.3java線程深度解析:線程的交互

線程交互式一個很復(fù)雜的問題

一.線程交互的基礎(chǔ)知識

線程交互知識點需要從java.lang.Object的類的三個方法來學(xué)習:

  1. void notify()
    喚醒在此對象監(jiān)視器上等待的單個線程氯哮。
  2. void notifyAll()
    喚醒在此對象監(jiān)視器上等待的所有線程。
  3. void wait()
    導(dǎo)致當前的線程等待商佛,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法喉钢。
    [注]以上三個都是objcect的方法,很多面試題會考察這個知識點良姆。


    object擁有的方法

當然肠虽,wait()還有另外兩個重載方法:
void wait(long timeout)
導(dǎo)致當前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法玛追,或者超過指定的時間量税课。
void wait(long timeout, int nanos)
導(dǎo)致當前的線程等待闲延,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程韩玩,或者已超過某個實際時間量垒玲。
wait(long timeout, int nanos)
以上這些方法是幫助線程傳遞線程關(guān)心的時間狀態(tài)。

關(guān)于等待/通知找颓,要記住的關(guān)鍵點是:
必須從同步環(huán)境內(nèi)調(diào)用wait()合愈、notify()、notifyAll()方法击狮。線程不能調(diào)用對象上等待或通知的方法佛析,除非它擁有那個對象的鎖。
wait()彪蓬、notify()寸莫、notifyAll()都是Object的實例方法。與每個對象具有鎖一樣寞焙,每個對象可以有一個線程列表储狭,他們等待來自該信號(通知)互婿。線程通過執(zhí)行對象上的wait()方法獲得這個等待列表捣郊。從那時候起,它不再執(zhí)行任何其他指令慈参,直到調(diào)用對象的notify()方法為止呛牲。如果多個線程在同一個對象上等待,則將只選擇一個線程(不保證以何種順序)繼續(xù)執(zhí)行驮配。如果沒有線程等待娘扩,則不采取任何特殊操作。
例子

/** 
* 計算輸出其他線程鎖計算的數(shù)據(jù) 
* 
* @author leizhimin 2008-9-15 13:20:38 
*/ 
public class ThreadA { 
    public static void main(String[] args) { 
        ThreadB b = new ThreadB(); 
        //啟動計算線程 
        b.start(); 
        //線程A擁有b對象上的鎖壮锻。線程為了調(diào)用wait()或notify()方法琐旁,該線程必須是那個對象鎖的擁有者 
        synchronized (b) { 
            try { 
                System.out.println("等待對象b完成計算。猜绣。灰殴。"); 
                //當前線程A等待 
                b.wait(); 
            } catch (InterruptedException e) { 
                e.printStackTrace(); 
            } 
            System.out.println("b對象計算的總和是:" + b.total); 
        } 
    } 
}
** 
* 計算1+2+3 ... +100的和 
* 
* @author leizhimin 2008-9-15 13:20:49 
*/ 
public class ThreadB extends Thread { 
    int total; 

    public void run() { 
        synchronized (this) { 
            for (int i = 0; i < 101; i++) { 
                total += i; 
            } 
            //(完成計算了)喚醒在此對象監(jiān)視器上等待的單個線程,在本例中線程A被喚醒 
            notify(); 
        } 
    } 
}

等待對象b完成計算掰邢。牺陶。。
b對象計算的總和是:5050
Process finished with exit code 0

線程通過執(zhí)行對象上的wait()方法獲得這個等待列表辣之。從那時候起掰伸,它不再執(zhí)行任何其他指令,直到調(diào)用對象的notify()方法為止怀估。如果多個線程在同一個對象上等待狮鸭,則將只選擇一個線程(不保證以何種順序)繼續(xù)執(zhí)行合搅。如果沒有線程等待,則不采取任何特殊操作歧蕉。
千萬注意:
當在對象上調(diào)用wait()方法時历筝,執(zhí)行該代碼的線程立即放棄它在對象上的鎖。然而調(diào)用notify()時廊谓,并不意味著這時線程會放棄其鎖梳猪。如果線程雖然在完成同步代碼,則線程在移出之前不會放棄鎖蒸痹。因此春弥,只要調(diào)用notify()并不意味著這時該鎖變得可用。

wait釋放鎖

b.wait()為什么會讓A線程等待叠荠?
請看b.wait()所在代碼塊匿沛,它是在synchronized (b) {..}中。分析以下程序流程就理解了:
(01) 執(zhí)行main()方法榛鼎,A線程啟動逃呼,進入“Running狀態(tài)”!
(02) new ThreadB()者娱;新建B線程抡笼,B線程進入“New 狀態(tài)”!
(03) 調(diào)用b.start()黄鳍,B線程進入“Runnable狀態(tài)”推姻。根據(jù)博主的運行結(jié)果,我們假設(shè)B線程進入“Runnable狀態(tài)”之后框沟,沒有立即進入“Running狀態(tài)”藏古。即,沒有立即執(zhí)行ThreadB的run()方法忍燥。
(04) A線程執(zhí)行synchronized (b){...}拧晕,獲取B線程的鎖。假設(shè)梅垄,此時B線程進入“Running狀態(tài)”,即執(zhí)行到run()方法的synchronized (b){...}厂捞;但由于B線程的鎖已被A線程獲取,所以B線程需要等待A線程釋放鎖哎甲。
(05) A線程中執(zhí)行b.wait()方法蔫敲,此時,A線程釋放B線程的鎖炭玫。B線程獲取鎖奈嘿,并進入“Running”狀態(tài);即執(zhí)行run()方法的synchronized (this) {...}吞加。而此時A線程的synchronized (b){...}需要等待“B線程通過notify()或notifyAll()喚醒”裙犹,并且同時A線程要“獲取鎖”才能繼續(xù)運行尽狠!
(05) B線程執(zhí)行notify()椎组,喚醒A線程乌妙;但A線程不能立即執(zhí)行,它需要等待B線程釋放鎖之后才能運行甫煞。
(06) B線程運行完畢掺冠,釋放鎖沉馆;A線程繼續(xù)運行,執(zhí)行b.wait()后面的代碼塊德崭。

二斥黑、多個線程在等待一個對象鎖時候使用notifyAll()

在多數(shù)情況下,最好通知等待某個對象的所有線程眉厨。如果這樣做锌奴,可以在對象上使用notifyAll()讓所有在此對象上等待的線程沖出等待區(qū),返回到可運行狀態(tài)憾股。

package cn.thread;

/**
 * 計算1+2+3 ... +100的和
 * 
 * @author 林計欽
 * @version 1.0 2013-7-23 上午10:06:04
 */
public class ThreadSum2 extends Thread {
    int total = 0;

    @Override
    public void run() {

        synchronized (this) {
            for (int i = 0; i < 101; i++) {
                total += i;
            }
            //通知所有在此對象上等待的線程 
            notifyAll();
        }

    }
}
package cn.thread;

/**
 * 線程的交互
 * 
 * @author 林計欽
 * @version 1.0 2013-7-23 上午10:04:11
 */
public class ThreadInteractionTest2 extends Thread{
    ThreadSum2 sum;
    
    public ThreadInteractionTest2(ThreadSum2 sum){
        this.sum=sum;
    }
    
    @Override
    public void run() {
        synchronized (sum) {
            try {
                System.out.println("等待對象sum完成計算鹿蜀。。服球。");
                // 當前線程ThreadInteractionTest等待
                sum.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("sum對象計算的總和是:" + sum.total);
        }
    }
    
    public static void main(String[] args) {
        ThreadSum2 sum = new ThreadSum2();
        
        //啟動三個線程茴恰,分別獲取計算結(jié)果 
        new ThreadInteractionTest2(sum).start();
        new ThreadInteractionTest2(sum).start();
        new ThreadInteractionTest2(sum).start();
        
        // 啟動計算線程
        sum.start();
        
        
    }
}

等待對象sum完成計算。有咨。琐簇。
等待對象sum完成計算蒸健。座享。。
等待對象sum完成計算似忧。渣叛。。
sum對象計算的總和是:5050
sum對象計算的總和是:5050
sum對象計算的總和是:5050

在我查閱博客的時候盯捌,發(fā)現(xiàn)Java線程:線程的交互中出現(xiàn)了bug淳衙,bug如下

Thread[Thread-1,5,main]等待計算結(jié)果。饺著。箫攀。
Thread[Thread-2,5,main]等待計算結(jié)果。幼衰。靴跛。
Thread[Thread-3,5,main]等待計算結(jié)果。渡嚣。梢睛。
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException: current thread not owner
at java.lang.Object.notifyAll(Native Method)
at threadtest.Calculator.run(Calculator.java:18)
Thread[Thread-1,5,main]計算結(jié)果為:5050
Thread[Thread-2,5,main]計算結(jié)果為:5050
Thread[Thread-3,5,main]計算結(jié)果為:5050
Process finished with exit code 0
然后文章分析了原因肥印,但是并沒有把解決方案寫上,寫了一個將绝葡,文章戛然而止深碱,出錯的原因在于

 public void run() { 
                synchronized (this) { 
                        for (int i = 0; i < 101; i++) { 
                                total += i; 
                        } 
                } 
                //通知所有在此對象上等待的線程 
                notifyAll(); 
        } 

應(yīng)該將notifyall加入到synchronized代碼同步塊中,這樣并不同步藏畅,notifyall()中應(yīng)該和wait()同步

談一下synchronized和wait()敷硅、notify()等的關(guān)系:
1、有synchronized的地方不一定有wait,notify
2愉阎、有wait,notify的地方必有synchronized.這是因為wait和notify不是屬于線程類竞膳,而是每一個對象都具有的方法,而且诫硕,這兩個方法都和對象鎖有關(guān)坦辟,有鎖的地方,必有synchronized章办。
另外锉走,注意一點:如果要把notify和wait方法放在一起用的話,必須先調(diào)用notify后調(diào)用wait藕届,因為如果調(diào)用完wait挪蹭,該線程就已經(jīng)不是current thread了。
當調(diào)用wait()后休偶,線程會釋放掉它所占有的“鎖標志”梁厉,從而使線程所在對象中的其它synchronized數(shù)據(jù)可被別的線程使用。

為什么notify(), wait()等函數(shù)定義在Object中踏兜,而不是Thread中词顾?
??Object中的wait(), notify()等函數(shù),和synchronized一樣碱妆,會對“對象的同步鎖”進行操作肉盹。
??wait()會使“當前線程”等待,因為線程進入等待狀態(tài)疹尾,所以線程應(yīng)該釋放它鎖持有的“同步鎖”上忍,否則其它線程獲取不到該“同步鎖”而無法運行!
OK纳本,線程調(diào)用wait()之后窍蓝,會釋放它鎖持有的“同步鎖”;而且繁成,根據(jù)前面的介紹吓笙,我們知道:等待線程可以被notify()或notifyAll()喚醒。現(xiàn)在朴艰,請思考一個問題:notify()是依據(jù)什么喚醒等待線程的观蓄?或者說混移,wait()等待線程和notify()之間是通過什么關(guān)聯(lián)起來的?答案是:依據(jù)“對象的同步鎖”侮穿。

負責喚醒等待線程的那個線程(我們稱為“喚醒線程”)歌径,它只有在獲取“該對象的同步鎖”(這里的同步鎖必須和等待線程的同步鎖是同一個),并且調(diào)用notify()或notifyAll()方法之后亲茅,才能喚醒等待線程回铛。雖然,等待線程被喚醒克锣;但是茵肃,它不能立刻執(zhí)行,因為喚醒線程還持有“該對象的同步鎖”袭祟。必須等到喚醒線程釋放了“對象的同步鎖”之后验残,等待線程才能獲取到“對象的同步鎖”進而繼續(xù)運行。

總之巾乳,notify(), wait()依賴于“同步鎖”您没,而“同步鎖”是對象鎖持有,并且每個對象有且僅有一個胆绊!這就是為什么notify(), wait()等函數(shù)定義在Object類氨鹏,而不是Thread類中的原因。

參考(轉(zhuǎn)載)博客
Java線程:線程的交互
Java多線程-線程的交互
Java線程詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末压状,一起剝皮案震驚了整個濱河市仆抵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌种冬,老刑警劉巖镣丑,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異碌廓,居然都是意外死亡传轰,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進店門谷婆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人辽聊,你說我怎么就攤上這事纪挎。” “怎么了跟匆?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵异袄,是天一觀的道長。 經(jīng)常有香客問我玛臂,道長烤蜕,這世上最難降的妖魔是什么封孙? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮讽营,結(jié)果婚禮上虎忌,老公的妹妹穿的比我還像新娘。我一直安慰自己橱鹏,他們只是感情好膜蠢,可當我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著莉兰,像睡著了一般挑围。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上糖荒,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天杉辙,我揣著相機與錄音,去河邊找鬼捶朵。 笑死奏瞬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的泉孩。 我是一名探鬼主播硼端,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寓搬!你這毒婦竟也來了珍昨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤句喷,失蹤者是張志新(化名)和其女友劉穎镣典,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唾琼,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡兄春,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锡溯。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片赶舆。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祭饭,靈堂內(nèi)的尸體忽然破棺而出芜茵,到底是詐尸還是另有隱情,我是刑警寧澤倡蝙,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布九串,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏猪钮。R本人自食惡果不足惜品山,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望烤低。 院中可真熱鬧肘交,春花似錦、人聲如沸拂玻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檐蚜。三九已至魄懂,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闯第,已是汗流浹背市栗。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留咳短,地道東北人填帽。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像咙好,于是被迫代替她去往敵國和親篡腌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,864評論 2 354