多線(xiàn)程在java中本身就是無(wú)處不在,例如Servlet天生就是多線(xiàn)程的。
1.JVM,有一些線(xiàn)程管理自身的常規任務(wù)(垃圾回收、終結處理)
2.Servlet,線(xiàn)程池
3.Timer
4.RMI
5.Swing和AWT
就算程序沒(méi)有顯示地創(chuàng )建任何線(xiàn)程,框架也會(huì )為你創(chuàng )建了一些線(xiàn)程!
1.更快的處理器vs更多的處理器并行運行。
2.從某種角度上來(lái)看,將單處理器上順序執行的程序,分成并發(fā)的程序,開(kāi)銷(xiāo)會(huì )更大,因為這會(huì )加上一些上下文切的代價(jià)。
但是在有阻塞(I/O)或者大計算的時(shí)候,如果沒(méi)有并發(fā),那么程序就會(huì )停止,有了并發(fā),其他任務(wù)還可以同時(shí)進(jìn)行。這里可以說(shuō)提高運行速度,不過(guò)另一個(gè)角度來(lái)看,也是為了更加公平,合理分配資源。
在發(fā)展的初期,計算機還沒(méi)有操作系統時(shí),自始自終只執行一個(gè)程序,這個(gè)程序直接訪(fǎng)問(wèn)機器的所有資源,每次只運行一個(gè),浪費資源(特別是有阻塞資源的時(shí)候)。
下面的原因促進(jìn)了操作系統支持多程序同時(shí)執行的發(fā)展:
1.更好的利用資源,比如等待的
2.公平。多個(gè)程序通過(guò)時(shí)間片方式來(lái)共享計算機。
3.方便。寫(xiě)一些程序完成不同的事情,比寫(xiě)一個(gè)程序完成所有程序要更加容易。
并發(fā)最直接的方式就是進(jìn)程,因為每個(gè)任務(wù)都作為進(jìn)程在其自己的地址空間中執行他們是相互隔離的,他們是并發(fā)的理想狀態(tài),因為任務(wù)之間根本不可能相互干涉,有些人提倡將進(jìn)程作為唯一合理的并發(fā)方式,但是,進(jìn)程通常會(huì )有數量和開(kāi)銷(xiāo)的限制。
上面提到了促進(jìn)多進(jìn)程發(fā)展的一些原因,其實(shí)就是這些相同的原因,也促進(jìn)了線(xiàn)程的發(fā)展,
它允許程序控制流(control flow)的多重分支同時(shí)存在于一個(gè)進(jìn)程,共享進(jìn)程內的資源,比如內存和文件句柄,但是每個(gè)線(xiàn)程都有自己的程序計數器、棧和本地變量,大多數現代操作系統把程序作為時(shí)序調度的基本單元,而不是進(jìn)程。
(參考:復習功課:對進(jìn)程、線(xiàn)程、應用程序域的理解
1、擴展java.lang.Thread類(lèi)。
此類(lèi)中有個(gè)run()方法,Thread 的子類(lèi)應該重寫(xiě)該方法。
或者也可以接受Runnable類(lèi)作為構造函數參數,直接啟動(dòng)!
2、實(shí)現java.lang.Runnable接口。
void run()
使用實(shí)現接口 Runnable 的對象創(chuàng )建一個(gè)線(xiàn)程時(shí),啟動(dòng)該線(xiàn)程將導致在獨立執行的線(xiàn)程中調用對象的 run 方法。
A.啟動(dòng)線(xiàn)程
在線(xiàn)程的Thread對象上調用start()方法,而不是run()或者別的方法。
在調用start()方法之后:發(fā)生了一系列復雜的事情,以前看到的圖,蠻不錯的:

B.線(xiàn)程休眠
Thread.sleep,讓線(xiàn)程休眠指定的毫秒數,也可以是納秒。當中斷他的時(shí)候有可能拋出InterruptedException。
新版的API有了一個(gè)更加顯式的版本,即TimeUnit。他是一個(gè)枚舉,他除了sleep方法,還有比較有用的比如timedWait和timedJoin等方法。
TimeUnit.MILLISECONDS.sleep(100).
C.線(xiàn)程調度機制及優(yōu)先級
setPriority(10)和Thread.yield()
D.后臺線(xiàn)程
所謂的后臺(daemon)線(xiàn)程,是指在程序運行的時(shí)候在后臺提供一種通用服務(wù)的線(xiàn)程,并且他不是不可或缺的一部分,因為,當所有非后臺線(xiàn)程結束時(shí),程序也就終止了,同時(shí)會(huì )殺死進(jìn)程中的所有后臺線(xiàn)程。注意后臺線(xiàn)程也不會(huì )執行finally方法,因為他是突然終止的。
E.等待一個(gè)線(xiàn)程
可以在一個(gè)線(xiàn)程里面調用另一個(gè)線(xiàn)程t的join方法,表明等待t,直到t結束!
在java5里面,java.util.concurrent類(lèi)庫中包含了CyclicBarrier(柵欄,大家都等待)這樣的工具,可能比join更加合適!。
F.捕獲線(xiàn)程的異常UncaughtExceptionHandler
由于線(xiàn)程的相對獨立的本性,因此不能從線(xiàn)程中獲取到為捕獲的異常,
Thread.UncaughtExceptionHandler是java5中的新接口,允許在每個(gè)Thread對象上都附著(zhù)一個(gè)異常處理器,uncaughtException(Thread t, Throwable e) 會(huì )在線(xiàn)程因未捕獲的異常而瀕臨死亡的時(shí)候調用!
G.經(jīng)驗
1、總是給自己的線(xiàn)程名字
2、獲取當前線(xiàn)程的對象的方法是:Thread.currentThread();
3、線(xiàn)程的調度是JVM的一部分,在一個(gè)CPU的機器上上,實(shí)際上一次只能運行一個(gè)線(xiàn)程。一次只有一個(gè)線(xiàn)程棧執行。JVM線(xiàn)程調度程序決定實(shí)際運 行哪個(gè)處于可運行狀態(tài)的線(xiàn)程
眾多可運行線(xiàn)程中的某一個(gè)會(huì )被選中做為當前線(xiàn)程??蛇\行線(xiàn)程被選擇運行的順序是沒(méi)有保障的。
利用多線(xiàn)程,大部分情況下都能得到更好的性能,但是缺也有非常大的風(fēng)險,但是如果并發(fā)狀況下,要訪(fǎng)問(wèn)某些共享數據,比如:
1.集合的CRU操作、一些符復合操作(i++)
2.某些關(guān)鍵資源的初始化,檢查再運行(check-then-act)
3.總的來(lái)看主要是:原子性、可見(jiàn)性、順序(指令重排)
從上面的線(xiàn)程狀態(tài)圖,可以看到,線(xiàn)程除了在Run的狀態(tài)外,其實(shí)還有很多種情況下會(huì )wait,比如sleep方法的time wait或者join的普通wait,以及鎖的block,所以在線(xiàn)程被hold住的時(shí)候我們能做什么?如果我們能夠從等待的狀態(tài)退出來(lái),這就叫中斷
正在處理的任務(wù),讓他停止,最簡(jiǎn)單的方式就是自己維持一個(gè)cancel的標志,類(lèi)似這樣:
private volatile boolean cancelled;
public void run(){
while(!cancelled){
//do sth
}
}
通過(guò)外部的某些接口來(lái)控制標志位,但是這種方式如果有阻塞的話(huà)就不行了,因此這時(shí)候根本不能走到判斷的代碼上。
最常見(jiàn)的,我們在sleep的時(shí)候總會(huì )要處理一個(gè)Interrupted異常
Thread類(lèi)包含interrupt方法,因此你可以終止被阻塞的任務(wù),這個(gè)方法就是設置線(xiàn)程的中斷狀態(tài);如果這個(gè)線(xiàn)程已經(jīng)阻塞,或者試圖執行一個(gè)阻塞操作,那么設置這個(gè)線(xiàn)程的中斷狀態(tài)將拋出InterruptedException!
但是并不是所有的情況下,調用interrupt,線(xiàn)程都會(huì )中斷,sleep,join等會(huì )響應,但是比如IO read或者鎖(synchronized)等情況就不會(huì )響應了
1.針對I/O,有一個(gè)麻煩,但是有效地方案,即通過(guò)外部方法直接關(guān)閉I/O.而不是靠中斷去關(guān)閉。(覆蓋Interrupt方法或者Future的Cancel方法)
2.另外,nio類(lèi)提供了可相應中斷的類(lèi),被阻塞的通道會(huì )自動(dòng)地響應中斷。Nio的非阻塞I/O也不支持可中斷i/o,但是可以通過(guò)關(guān)閉通道或者請求Selector上的喚醒來(lái)取消阻塞操作
3.對于鎖的阻塞,不同線(xiàn)程去獲取鎖的時(shí)候會(huì )阻塞,一個(gè)線(xiàn)程內,調用兩個(gè)syn的方法是不會(huì )阻塞的,因為鎖是可重入的,當前線(xiàn)程已經(jīng)獲得了鎖。另外,如果是用Lock去獲取鎖的話(huà),那么它會(huì )可以響應中斷。
4.新版的API可以在Executor上調用shutdownNow,他將發(fā)送一個(gè)interrupt調用給他啟動(dòng)的所有線(xiàn)程。
5.如果中斷單一的任務(wù),可以使用Executor的submit方法來(lái)啟動(dòng)任務(wù),就可以持有Future對象,調用它的cancel方法。
還記得在前面說(shuō)的,自己維護一個(gè)cancel標志位來(lái)處理中斷,其實(shí)那是完全不必要的,每個(gè)線(xiàn)程都有一個(gè)與之關(guān)聯(lián)的Boolean屬性,表示線(xiàn)程的中斷狀態(tài),初始為false,調用Thread.interrupt中斷一個(gè)線(xiàn)程時(shí),會(huì )出現兩種情況:
1.如果那個(gè)線(xiàn)程在執行一個(gè)低級的可中斷的阻塞方法,例如Thread.sleep、join、Object.wait,那么他將取消阻塞,重置標志位,并且拋出異常;
2.否則,interrupt只是設置線(xiàn)程的中斷狀態(tài),被中斷的線(xiàn)程,可以通過(guò)調用interrupted來(lái)輪詢(xún)中斷狀態(tài),interrupted方法還會(huì )同時(shí)清除中斷狀態(tài),確保不會(huì )通知你兩次。如果只是單獨的查詢(xún),可以使用Threaad.isInterrupted方法!
保留中斷狀態(tài)
有些任務(wù)拒絕被中斷,這使得它們是不可取消的。但是,即使是不可取消的任務(wù)也應該嘗試保留中斷狀態(tài),以防在不可取消的任務(wù)結束之后,調用棧上更高層的代碼需要對中斷進(jìn)行處理(想象我們依賴(lài)于interrupted,但是經(jīng)過(guò)別人處理之后,這個(gè)狀態(tài)就被吞了,那是多么坑爹)。
下面 展示了一個(gè)方法,該方法等待一個(gè)阻塞隊列,直到隊列中出現一個(gè)可用項目,而不管它是否被中斷。為了方便他人,它在結束后在一個(gè) finally 塊中恢復中斷狀態(tài),以免剝奪中斷請求的調用者的權利。(它不能在更早的時(shí)候恢復中斷狀態(tài),因為那將導致無(wú)限循環(huán) —— BlockingQueue.take() 將在入口處立即輪詢(xún)中斷狀態(tài),并且,如果發(fā)現中斷狀態(tài)集,就會(huì )拋出 InterruptedException。)
public Task getNextTask(BlockingQueue<Task> queue) {
boolean interrupted = false;
try {
while (true) {
try {
return queue.take();
} catch (InterruptedException e) {
interrupted = true;
// fall through and retry
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
}
并發(fā)之后,如果有共享資源,必然會(huì )有線(xiàn)程安全的問(wèn)題,參考前面的【1.5 多線(xiàn)程的風(fēng)險】
防止多個(gè)線(xiàn)程多字段訪(fǎng)問(wèn)的方法就是加鎖,第一個(gè)訪(fǎng)問(wèn)這個(gè)資源的任務(wù)必須獲得這項資源,使其他任務(wù)在解鎖之前,無(wú)法訪(fǎng)問(wèn)他!
1.同步方法的鎖
2.對象鎖
public void test(){
int i=0;
//this表示當前的這個(gè)對象的鎖
synchronized(this){
i++;
}
}
對象的內部鎖和它的狀態(tài)之間沒(méi)有任何關(guān)系,即時(shí)獲得了對象關(guān)聯(lián)的鎖也不能阻止其他線(xiàn)程訪(fǎng)問(wèn)這個(gè)對象,獲得鎖后唯一可以做的是阻止其他線(xiàn)程再次獲得相同的鎖;
Java可以通過(guò)對象鎖來(lái)實(shí)現互斥,允許多個(gè)線(xiàn)程在同一個(gè)共享數據上獨立。所有對象都自動(dòng)含有單一的鎖(也稱(chēng)為是監視器),當在對象上調用任意synchronized方法的時(shí)候,此對象都被加鎖。

當一個(gè)線(xiàn)程到了一個(gè)監視區域的開(kāi)始處,他們就會(huì )被放置到該監視器的入口區,就像是走廊,如果沒(méi)有其他線(xiàn)程在入口區中等待,也沒(méi)有線(xiàn)程正持有該監視器,則這個(gè)線(xiàn)程就可以獲得該監視器,并且繼續執行監視區域中的代碼。如果已經(jīng)有其他線(xiàn)程在入口區中等待,則這個(gè)線(xiàn)程也必須在那里等待,直到有退出,并且會(huì )有一個(gè)線(xiàn)程獲取監視器,這個(gè)是不確定的。
鎖主要提供了兩種特性:互斥性和可見(jiàn)性。
1.互斥一次只允許一個(gè)線(xiàn)程持有某個(gè)特定的鎖,因此可以使用該特性實(shí)現對共享數據的協(xié)調訪(fǎng)問(wèn)協(xié)議,這樣,一次就只有一個(gè)線(xiàn)程能夠使用該共享數據;
2.可見(jiàn)性和java內存模型有關(guān),鎖保證釋放之前對共享數據作出的修改對于隨后獲得該鎖的另一個(gè)線(xiàn)程是可見(jiàn)的。
3.允許重排序
reordering inside synchronized block
如果syn內部的代碼塊沒(méi)有依賴(lài)的話(huà),是可以在syn內部重排序的,但是不會(huì )跑到外面。
另外synchronized也是可重進(jìn)入的
Synchronized確保了共享變量的原子性和可見(jiàn)性,它能夠實(shí)現同步,但是,它也有一些限制:無(wú)法中斷一個(gè)正在等候獲得鎖的線(xiàn)程,也無(wú)法通過(guò)投票得到鎖。

JDK1.5以后的Lock是鎖的一個(gè)抽象,它允許把鎖定的實(shí)現作為Java類(lèi),而不是語(yǔ)言的特性來(lái)實(shí)現。就為Lock的多種實(shí)現留下了空間,各種實(shí)現可以有不同的調度算法,性能特性或者鎖定語(yǔ)義。
Syn的范圍機制使用起來(lái)更加簡(jiǎn)單,可以避免很多編程錯誤,只要將方法加上syn或者在代碼塊上涌syn{}括起來(lái),就會(huì )完成加鎖,解鎖等所有功能;Lock能完成syn的功能,同時(shí)他更加靈活,可以讓你在自己需要的范圍以及順序來(lái)請求和釋放鎖;當然靈活性也意味著(zhù)更加復雜,我們需要顯示的獲得和釋放鎖:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();//必須放在finally中,確保被釋放
}
靈活性不僅體現在范圍控制的靈活性上,還體現在鎖獲得方式的靈活性。
tryLock方法,如果無(wú)法獲得鎖的話(huà),返回false,這可以用來(lái)實(shí)現輪訓的鎖請求
還可以帶上一個(gè)超時(shí)參數tryLock(long,TimeUnit),這可以用來(lái)實(shí)現定時(shí)的鎖請求
響應中斷的lockInterruptibly;可中斷的鎖請求
ReentrantLock鎖和syn塊有相同的語(yǔ)義,它可以認為是Lock的最基本實(shí)現。他構造函數中的參數true/false,決定他是否是公平鎖,公平鎖會(huì )選擇等待時(shí)間最長(cháng)的線(xiàn)程執行,不過(guò)公平鎖的性能是最差的,內部鎖也是非公平鎖,可以參考:ReentrantLockTest。
ReentrantLock類(lèi)實(shí)現了Lock,它擁有與synchronized相同的并發(fā)性和內存語(yǔ)義,但是他添加了類(lèi)似鎖投票,定時(shí)鎖等候和可中斷鎖等候的一些特性。不過(guò)它在使用的時(shí)候必須在finally中unLock,另外JVM用synchronized管理鎖定請求和釋放時(shí),JVM在生成線(xiàn)程轉存儲時(shí)能夠包括鎖定信息,這樣對調試很有用。Lock只是普通的類(lèi),JVM不知道具體哪個(gè)線(xiàn)程擁有Lock對象。
所以在大多數情況下,使用synchronizeed更好,除非確實(shí)需要synchronized沒(méi)有的特性,比如時(shí)間鎖等候,可中斷鎖等候、無(wú)塊結構鎖、和鎖投票等。

Java5中ReentrantLock的性能優(yōu)于Syn,Java6的syn進(jìn)行了改善,兩者已經(jīng)比較接近
ReentrantLock構造器的一個(gè)參數是boolean值,它允許選擇一個(gè)公平(fair)鎖,還是一個(gè)不公平鎖(允許闖入)。公平鎖使線(xiàn)程按照請求順序依次獲得鎖,不公平鎖并不是按順序來(lái)的。
CPU的線(xiàn)程調度本來(lái)就是不公平的,JVM保證了所有線(xiàn)程都會(huì )得到他們所等候的鎖,大多數情況下,確保所得公平性的成本非常高,比synchronized高的多,所以默認情況下,使用false的參數就可以了。
在競爭激烈的情況下,非公平的鎖性能較好,可能的原因之一是:因為“掛起一個(gè)線(xiàn)程和重新開(kāi)始運行”是一個(gè)比較耗時(shí)的操作。假設A持有鎖,B請求然后被掛起,之后A釋放鎖,如果此時(shí)有C請求鎖,那么C就能直接獲得,甚至在B被喚醒前C就已經(jīng)釋放了,這樣B沒(méi)有晚得到鎖,C也更早的得到了鎖
但是如果持有鎖的時(shí)間比較長(cháng),或者請求鎖的平均間隔較長(cháng),這時(shí)候可能公平鎖也不錯,因為闖入的優(yōu)勢:在線(xiàn)程被喚醒的過(guò)程中(還沒(méi)有得到鎖),被其他人得到,不容易出現。
ReentrantLock大部分的代碼都是sync實(shí)現的,ReentrantLock只是簡(jiǎn)單的執行轉發(fā)而已,

FairSync和NonFairSync都是ReentrantLock的靜態(tài)內部類(lèi)。Sync 是一個(gè)抽象類(lèi),而FairSync和NonFairSync則是具體類(lèi),分別對應了公平鎖和非公平鎖。
各種不同的Lock,功能更加豐富!
當然,還有另外一些鎖允許并發(fā)訪(fǎng)問(wèn),比如ReadWriteLock!
AbstractQueuedSynchronizer源碼解析之ReentrantReadWriteLock
《多處理器編程的藝術(shù)》讀書(shū)筆記(7)--- CLH隊列鎖
深入淺出多線(xiàn)程系列之十五:Reader /Write Locks (讀寫(xiě)鎖)
維護了一對相關(guān)的鎖,一個(gè)用于只讀操作,另一個(gè)用于寫(xiě)入操作,只要沒(méi)有writer,讀取鎖可以由多個(gè)reader線(xiàn)程同時(shí)保持。寫(xiě)入鎖是獨占地。
與互斥鎖相比,讀-寫(xiě)鎖允許對共享數據進(jìn)行更高級別的訪(fǎng)問(wèn)。雖然依次只有一個(gè)線(xiàn)程可以修改共享數據,但在許多情況下,任何數量的線(xiàn)程可以同時(shí)讀取共享數據。
例如,某個(gè)數據填充后,不經(jīng)常對其進(jìn)行修改,因為不經(jīng)常對其進(jìn)行修改,所以這種情況,用讀-寫(xiě)鎖筆記哦合適。
針對鎖的實(shí)現,除了要保證鎖的性質(zhì)(互斥、無(wú)死鎖、)之外,都會(huì )面臨一個(gè)問(wèn)題,就是不能獲取鎖的時(shí)候怎么做?一種方案是繼續嘗試,這成為自旋鎖。反復嘗試的過(guò)程稱(chēng)為旋轉或忙等待;另外一種方案是掛起線(xiàn)程,這種方案稱(chēng)為阻塞。
許多的實(shí)現都是將兩種策略結合使用,先旋轉一小段時(shí)間然后再阻塞。比如java里的AQS
當使用多個(gè)線(xiàn)程來(lái)同時(shí)運行多個(gè)任務(wù)的時(shí)候,可以使用鎖來(lái)同步兩個(gè)任務(wù)的行為,這樣保證不會(huì )相互干擾。但是,有些任務(wù)可能需要線(xiàn)程之間協(xié)作解決,這不再是彼此之間的干涉,而是彼此之間的協(xié)調!
讓這些線(xiàn)程協(xié)作,關(guān)鍵就是握手,這可以通過(guò)基礎特性:互斥,可以確保只有一個(gè)任務(wù)可以響應某個(gè)信號,在互斥的基礎上,還有一個(gè)途徑,可以將自己掛起,直到外部條件發(fā)生變化!
這可以用Object的wait和notify方法來(lái)安全的實(shí)現,另外,JAVA5還提供了具有await和signal方法的Condition對象!
一些參考
在并發(fā)程序中,有一些操作需要判斷某個(gè)狀態(tài),如果不滿(mǎn)足則阻塞,直到條件為真,再恢復。對于這些狀態(tài)檢查的代碼,最原始的方式是這樣:
Public v take(){
While(true){
Synchronized(this){
If(!isEmpty())
Return doTake();
}
Thread.sleep(1000);
}
}
上面的方式利用輪詢(xún)加休眠的方式來(lái)實(shí)現阻塞,但是有個(gè)問(wèn)題,sleep容易睡過(guò)頭,也就是說(shuō)狀態(tài)已經(jīng)改變了,但是還在sleep里面;休眠時(shí)間越短,響應性越好;但是CPU消耗的更高;這種方式很不好,條件隊列可以做這樣的事情。
條件隊列可以讓一組線(xiàn)程等待一些相關(guān)條件,直到條件為真;Java每個(gè)對象中都有內置鎖,每個(gè)內置鎖中也有內部等待隊列,wait/notify/notifyAll構成了內部條件隊列的API。使用這種方式,相對于輪詢(xún)加休眠的方式,主要是多方面有了優(yōu)化,CPU效率,上下文切換開(kāi)銷(xiāo)、響應性!理論上來(lái)說(shuō),用輪詢(xún)+休眠的方式無(wú)法完成的事情,條件隊列也無(wú)法完成!
如果條件不為真(緩存為空),那么take等待,直到另一個(gè)線(xiàn)程在緩存中置入一個(gè)對象;這時(shí),需要把take對應的”線(xiàn)程“先放到一個(gè)隊列里,等有數據了,再從隊列里拿出來(lái),就是wait方法,wait方法會(huì )將當前線(xiàn)程放入對應對象鎖的阻塞隊列(所以wait要放在syn塊里,應該先持有該對象的鎖),釋放對象鎖之后,阻塞當前線(xiàn)程,然后等待,直到特定時(shí)間的超時(shí)過(guò)后,或者被通知喚醒!
喚醒之后會(huì )返回運行前重新請求對象鎖,一個(gè)從wait方法喚醒的線(xiàn)程,在重新請求鎖的過(guò)程中,沒(méi)有任何特殊的優(yōu)先級,她像在任何其他嘗試進(jìn)入synchronized快的線(xiàn)程一樣去爭奪鎖!
wait和sleep有兩個(gè)顯著(zhù)的不同:
1.Wait期間鎖是釋放的
2.可以通過(guò)notify/notifyAll或者時(shí)間到期,讓wait恢復執行
前面已經(jīng)說(shuō)過(guò),獲得鎖有一個(gè)等待區域,每一個(gè)同步鎖lock下面都掛了幾個(gè)線(xiàn)程隊列,包括就緒(Ready)隊列,等待(Waiting)隊列(看3.2的圖)等。當線(xiàn)程A因為得不到同步鎖lock,從而進(jìn)入的是lock.ReadyQueue(就緒隊列),一旦同步鎖不被占用,JVM將自動(dòng)運行就緒隊列中的線(xiàn)程而不需要任何notify()的操作。但是當線(xiàn)程A被wait()了,那么將進(jìn)入lock.WaitingQuene(等待隊列),同時(shí)占據的同步鎖也會(huì )放棄。而此時(shí)如果同步鎖不喚醒等待隊列中的進(jìn)程(lock.notify()),這些進(jìn)程將永遠不會(huì )得到運行的機會(huì )。
為什么,wait和notify是Object的方法,而不是線(xiàn)程的?
因為鎖,隊列都是在對象上的(每個(gè)對象都有個(gè)監視器),因此,wait和notify都是object的方法,方法會(huì )操作的是這個(gè)監視器的隊列(就緒隊列/等待隊列),所以不是線(xiàn)程的方法
一個(gè)鎖的等待隊列中可能會(huì )有多個(gè)等待線(xiàn)程,他們可能是因為相同或者不同的條件在等待,調用notifyAll/notify的時(shí)候會(huì )有線(xiàn)程A被喚醒,不過(guò)也許這個(gè)通知時(shí)針對另一個(gè)線(xiàn)程B發(fā)出的,所以你過(guò)早的被喚醒了,并且再也回不去了!
因此,當你從wait中喚醒之后,都必須再次測試條件,如果不為真,就繼續等待,因此你永遠應該在while內部調用wait:
Synchronized(lock){
While(!condition)
Lock.Wait();
}
廚師和服務(wù)員是經(jīng)典的消費者,生產(chǎn)者的例子,服務(wù)員等待廚師做好食物,廚師準備好以后會(huì )通知服務(wù)員,服務(wù)員上菜以后繼續等待,這是一個(gè)任務(wù)協(xié)作的過(guò)程,可以用wait/notify實(shí)現。
Wait和notify依賴(lài)于一個(gè)鎖可以實(shí)現生產(chǎn)者/消費者模型,就是使用的時(shí)候要注意一定要放在一個(gè)syn塊里
以前為公司出的一道面試題,有點(diǎn)偏,有興趣的可以試試
Wait和notify以一種非常低級的方式解決了任務(wù)互操作的問(wèn)題,即每次操作的時(shí)候都握手。不過(guò)可以使用更高級別的操作,同步隊列來(lái)解決線(xiàn)程協(xié)作的問(wèn)題。

BlockingQueue有大量標準的實(shí)現,比如LinkedBlockingQueue,一個(gè)無(wú)界隊列,ArrayBlockingQueue,她有固定尺寸,超過(guò)了會(huì )阻塞!
聯(lián)系客服