線程交互式一個很復(fù)雜的問題
一.線程交互的基礎(chǔ)知識
線程交互知識點需要從java.lang.Object的類的三個方法來學(xué)習:
- void notify()
喚醒在此對象監(jiān)視器上等待的單個線程氯哮。 - void notifyAll()
喚醒在此對象監(jiān)視器上等待的所有線程。 -
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線程詳解