一秃流、進(jìn)程
在多進(jìn)程設(shè)計(jì)中各進(jìn)程之間的數(shù)據(jù)塊是相互獨(dú)立赂蕴,彼此通過信號(hào)、管道進(jìn)行通信舶胀。而在多線程設(shè)計(jì)中概说,各線程不一定獨(dú)立,同一任務(wù)中的各線程共享程序段嚣伐、數(shù)據(jù)段等資源糖赔。Java通過Package類(Java.lang.package)支持多進(jìn)程,通過Thread類支持多線程轩端。
二放典、線程
多線程既有生產(chǎn)者消費(fèi)者,哲學(xué)家就餐,讀寫器或者簡單的有界緩沖區(qū)等應(yīng)用問題奋构。也有死鎖壳影,競態(tài)條件,內(nèi)存沖突和線程安全等并發(fā)問題弥臼。
為方便寫出線程安全的程序宴咧,Java提供線程安全類和并發(fā)工具,比如:同步容器径缅、并發(fā)容器悠汽、隊(duì)列、同步工具類芥驳。
三柿冲、多線程環(huán)境解決方案及原理
基本上所有的并發(fā)模式都是采用串行訪問共享資源的方案解決線程沖突問題。
Java語言的同步機(jī)制
兆旬,在底層
實(shí)現(xiàn)有兩種方式:互斥
和協(xié)同
假抄。在語言層面
,就是內(nèi)置鎖synchronized
和內(nèi)置條件隊(duì)列
丽猬,即Object的wait()宿饱,notify(),notifyAll()方法脚祟。
顯式鎖為Lock谬以,顯示條件隊(duì)列為Condition對(duì)象。
@TreadSafe
public class BoundedBuffer<V> extends BaseBoundedBuffer<V>{
public BoundedBuffer(int size){
super(size);
}
public synchronized void put(V v) throws InterruptedException{
while ( isFull){
wait();
}
doPut(v);
notifyAll();
}
public synchronized V take() throws InterruptedException{
while( isEmpty()){
wait();
}
V v = doTake();
notifyAll();
retur v;
}
}
四由桌、鎖
synchronized
在Java語言中有兩種內(nèi)建的synchronized語法:synchronized語句为黎、synchronized方法。
synchronized語句:當(dāng)源代碼被編譯成字節(jié)碼的時(shí)候行您,會(huì)在同步塊的入口位置和出口分別插入monitorenter和monitorexit字節(jié)碼指令铭乾。
synchronized方法:在Class文件的方法表中將該方法的access_flags字段中的synchronized標(biāo)志位設(shè)置為1。
每個(gè)對(duì)象都有一個(gè)鎖娃循,也就是監(jiān)視器(monitor)炕檩。當(dāng)monitor被占有時(shí)表示它被鎖定。線程執(zhí)行monitorenter指令時(shí)嘗試獲取對(duì)象所對(duì)應(yīng)的monitor的所有權(quán)捌斧。
相同點(diǎn):Lock能完成synchronized的相同功能笛质。
synchronized | java.util.concurrent.locks.Lock | |
---|---|---|
原理 | 在對(duì)象頭設(shè)置標(biāo)記 | Lock接口的實(shí)現(xiàn)類只用volatile修飾的int變量保證每個(gè)線程都擁有對(duì)該int的可見性和原子修改。 |
自動(dòng)釋放鎖 | 必需手工在finally從句中釋放捞蚂。 | |
可 定時(shí)妇押,中斷、公平鎖洞难、非阻塞
|
ReentrantLock
ReentrantLock利用CAS+CLH隊(duì)列來實(shí)現(xiàn)舆吮。支持公平鎖和非公平鎖。
CAS:Compare and Swap队贱,比較并交換色冀。CLH隊(duì)列:帶頭結(jié)點(diǎn)的雙向非循環(huán)鏈表。
ReentrantLock的實(shí)現(xiàn):先通過CAS嘗試獲取鎖柱嫌。如果鎖已經(jīng)被占據(jù)锋恬,就加入CLH隊(duì)列并被掛起。當(dāng)鎖被釋放后编丘,排在CLH隊(duì)首的線程會(huì)被喚醒与学,然后CAS再次嘗試獲取鎖。在這個(gè)時(shí)候嘉抓,非公平鎖
:如果同時(shí)還有另一個(gè)線程進(jìn)來嘗試獲取索守,那么有可能會(huì)讓這個(gè)線程搶先獲取。公平鎖
:如果同時(shí)還有另一個(gè)線程進(jìn)來嘗試獲取抑片,當(dāng)它發(fā)現(xiàn)自己不是在隊(duì)首的話卵佛,就會(huì)排到隊(duì)尾,由隊(duì)首的線程獲取到鎖敞斋。
Condition
Condition和Lock關(guān)聯(lián)使用截汪,就像條件隊(duì)列和內(nèi)置鎖相關(guān)聯(lián)一樣。在相關(guān)聯(lián)的Lock上調(diào)用Lock.newCondition()創(chuàng)建Condition植捎。
Condition比內(nèi)置條件隊(duì)列提供更豐富的功能:在每個(gè)鎖上可存在多個(gè)等待衙解,條件等待是可中斷的或者不可中斷的、基于時(shí)限的焰枢,以及公平的或非公平的隊(duì)列操作蚓峦。
與內(nèi)置條件隊(duì)列不同的是,對(duì)于每個(gè)Lock济锄,可以有任意數(shù)量的Condition對(duì)象枫匾。Condition對(duì)象繼承了相關(guān)的Lock對(duì)象的公平性,對(duì)于公平的鎖拟淮,線程會(huì)依照FIFO順序從Condition.await中釋放干茉。
注意:在Condition對(duì)象中,與wait,notify和notifyAll方法對(duì)應(yīng)的分別是await,signal,signalAll很泊。但是角虫,Condition繼承了Object,因而它也包含wait和notify方法委造。一定要確保使用的版本——await和signal戳鹅。
鎖的高級(jí)特性
可重入:線程的可重入,是指外層函數(shù)獲得鎖之后昏兆,內(nèi)層也可以獲得鎖枫虏。ReentrantLock和synchronized都是可重入鎖。
驚群效應(yīng)(Herd Effect):占有鎖的線程釋放后,所有等待獲取鎖的競爭者同時(shí)被喚醒隶债,都嘗試搶占鎖腾它。
公平鎖和非公平鎖:非公平鎖普遍比公平鎖開銷小。但如果必須要鎖的競爭者按順序獲得鎖死讹,那么就需要實(shí)現(xiàn)公平鎖瞒滴。
阻塞鎖和自旋鎖:阻塞鎖會(huì)有上下文切換,如果并發(fā)量比較高且臨界區(qū)的操作耗時(shí)比較短赞警,那么造成的性能開銷就比較大妓忍。如果臨界區(qū)操作耗時(shí)比較長,一直保持自旋愧旦,也會(huì)對(duì)CPU造成更大的負(fù)荷世剖。
鎖的對(duì)象
實(shí)例同步方法,鎖是當(dāng)前實(shí)例對(duì)象
笤虫。
靜態(tài)同步方法搁廓,鎖是當(dāng)前對(duì)象的Class對(duì)象
耕皮。
同步方法塊,鎖是synchonized括號(hào)里的對(duì)象
凌停。
死鎖
死鎖是指多個(gè)線程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象罚拟,若無外力作用台诗,它們都將無法推進(jìn)下去赐俗。必須滿足以下四個(gè)條件才發(fā)生死鎖:
-
互斥
:一個(gè)資源每次只能被一個(gè)線程使用。 -
請(qǐng)求與保持
:一個(gè)線程因請(qǐng)求資源而阻塞時(shí)阻逮,不釋放已獲得的資源粱快。 -
不剝奪
:線程已獲得的資源,在末使用完之前叔扼,不能被強(qiáng)行剝奪。 -
循環(huán)等待
:若干線程之間形成循環(huán)等待資源關(guān)系瓜富。
避免死鎖最簡單的方法就是阻止循環(huán)等待條件,將系統(tǒng)中所有的資源設(shè)置標(biāo)志位并排序谤辜,規(guī)定所有的進(jìn)程申請(qǐng)資源必須按順序進(jìn)行。
避免死鎖的通用經(jīng)驗(yàn)法則是:當(dāng)要訪問共享資源A涡戳、B渠欺、C 時(shí)椎眯,保證每個(gè)線程都按同樣的順序訪問共享資源。
活鎖
處于活鎖的線程的狀態(tài)是不斷改變的舔稀,活鎖可以認(rèn)為是一種特殊的饑餓掌测。一個(gè)現(xiàn)實(shí)的活鎖例子是兩個(gè)人在走廊碰到,兩個(gè)人都試著避讓對(duì)方好讓彼此通過夜郁,但是因?yàn)楸茏尩姆较蚨家粯訉?dǎo)致最后誰都不能前進(jìn)粘勒。
饑餓
鎖降級(jí)
鎖降級(jí)是指寫鎖降級(jí)成讀鎖。如果當(dāng)前線程擁有寫鎖事富,然后將其釋放乘陪,最后獲取讀鎖,這種分段完成的過程不能稱之為鎖降級(jí)贱勃。鎖降級(jí)是指把持装啤(當(dāng)前擁有的)寫鎖,再獲取到讀鎖拔鹰,最后釋放(先前擁有的)寫鎖的過程贵涵。
鎖降級(jí)中的讀鎖是否有必要呢恰画?答案是必要瓷马。主要是為了保證數(shù)據(jù)的可見性,如果當(dāng)前線程不獲取讀鎖而是直接釋放寫鎖片林,假設(shè)此刻另一個(gè)線程(T)獲取了寫鎖并修改了數(shù)據(jù)怀骤,那么當(dāng)前線程無法感知線程T的數(shù)據(jù)更新。如果當(dāng)前線程獲取讀鎖弓摘,即遵循鎖降級(jí)的步驟痕届,則線程T將會(huì)被阻塞研叫,直到當(dāng)前線程使用數(shù)據(jù)并釋放讀鎖之后,線程T才能獲取寫鎖進(jìn)行數(shù)據(jù)更新嚷炉。
讀寫鎖ReentrantReadWriteLock
讀寫鎖有兩個(gè)鎖渤昌,一個(gè)是讀操作相關(guān)的鎖,稱為共享鎖独柑;另一個(gè)是寫操作相關(guān)的鎖忌栅,也叫排它鎖。多個(gè)讀鎖之間不互斥湖员,讀鎖與寫鎖互斥瑞驱,寫鎖與寫鎖互斥。讀寫鎖是用來提升并發(fā)程序性能的鎖分離
技術(shù)的成果凳寺。
悲觀鎖
悲觀鎖假設(shè)最壞的情況,并且只有在確保其他線程不會(huì)干擾(通過獲取正確的鎖)的情況下才能執(zhí)行下去逆趋。
常見實(shí)現(xiàn)如獨(dú)占鎖晒奕。
安全性高,但在中低并發(fā)程度下的效率低魄眉。
樂觀鎖
樂觀鎖借助沖突檢查機(jī)制來判斷在更新過程中是否存在其他線程的干擾漾橙,如果存在楞卡,這個(gè)操作將失敗蒋腮,并且可以重試(也可以不重試)。
常見實(shí)現(xiàn)如CAS等池摧。
部分樂觀鎖削弱了一致性作彤,但提高了中低并發(fā)程度下的效率。
五竭讳、java線程間通信之條件隊(duì)列
條件隊(duì)列中存儲(chǔ)的是"處于等待狀態(tài)的線程"绢慢,這些線程在等待某種條件變成真。正如每個(gè)Java對(duì)象都可以作為一個(gè)鎖骚露,每個(gè)對(duì)象同樣可以作為一個(gè)條件隊(duì)列缚窿,這個(gè)對(duì)象的wait,notify,notifgAll就構(gòu)成了內(nèi)部條件隊(duì)列的API。對(duì)象的內(nèi)置鎖與條件隊(duì)列是相互關(guān)聯(lián)的够话。要調(diào)用條件隊(duì)列的任何一個(gè)方法,必須先持有該對(duì)對(duì)象上的鎖畜份。
"條件隊(duì)列中的線程一定是執(zhí)行不下去了才處于等待狀態(tài)"欣尼,這個(gè)"執(zhí)行不下去的條件"叫做"條件謂詞"。
wait()方法的返回并不一定意味著正在等待的條件謂詞變成真了钙态。
舉個(gè)列子:假設(shè)現(xiàn)在有三個(gè)線程在等待同一個(gè)條件謂詞變成真册倒,然后另外一個(gè)線程調(diào)用了notifyAll()方法磺送。此時(shí),只能有一個(gè)線程離開條件隊(duì)列崇呵,另外兩個(gè)線程將仍然需要處于等待狀態(tài)馅袁,這就是在代碼中使用while(conditioin is not true){this.wait();}而不使用if(condition id not true){this.wait();}的原因。
另外一種情況是:同一個(gè)條件隊(duì)列與多個(gè)條件謂詞互相關(guān)聯(lián)犹褒。這個(gè)時(shí)候弛针,當(dāng)調(diào)用此條件隊(duì)列的notifyAll()方法時(shí)钦奋,某些條件謂詞根本就不會(huì)變成真。
在本文的例子中付材,可以看到使用while而不是if來判斷條件謂詞是否為空厌衔,就是基于以上幾種原因的考慮。用一句話來概括:“每當(dāng)線程被從wait中喚醒時(shí)睬隶,都必須再次測試條件謂詞”,切記银萍,下面是條件等待的標(biāo)準(zhǔn)形式:
void xxxMethod() throws InterruptedException{
synchronized(lock){
while(!conditionPredition)
lock.wait();
doSomething();
}
}
六恤左、線程
終止線程的三種方法
- 使用
volatile 布爾變量
作為退出標(biāo)志,使線程正常退出戳气,也就是當(dāng)run方法完成后線程終止巧鸭。 - 使用stop方法強(qiáng)行終止線程,不推薦此方法呀袱,因?yàn)閟top和suspend及resume都是作廢過期的方法巷折,使用它們可能造成數(shù)據(jù)狀態(tài)不一致锻拘。
- 使用
interrupt方法
中斷線程击蹲。(推薦
)
線程狀態(tài)
- 新建狀態(tài): 用new語句創(chuàng)建的線程對(duì)象處于新建狀態(tài)歌豺,此時(shí)它和其它的java對(duì)象一樣,僅在堆中被分配了內(nèi)存类咧。
- 就緒狀態(tài)(New): 創(chuàng)建了線程后痕惋,調(diào)用它的start()方法,該線程就進(jìn)入就緒狀態(tài)议谷。處于這個(gè)狀態(tài)的線程位于可運(yùn)行池中堕虹,等待獲得CPU的使用權(quán)芬首。
- 運(yùn)行狀態(tài)(Runnable): 處于這個(gè)狀態(tài)的線程占用CPU郁稍,執(zhí)行程序的代碼胜宇。
- 阻塞狀態(tài)(Blocked): 當(dāng)線程處于阻塞狀態(tài)時(shí),java虛擬機(jī)不會(huì)給線程分配CPU封寞,直到線程重新進(jìn)入就緒狀態(tài)仅财,它才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)盏求。
阻塞狀態(tài)分為三種情況
1) 位于對(duì)象等待池
中的阻塞狀態(tài): 當(dāng)線程運(yùn)行時(shí),如果執(zhí)行了某個(gè)對(duì)象的wait()方法磅废,JVM就會(huì)把這個(gè)線程放到這個(gè)對(duì)象的等待池中荆烈。
2) 位于對(duì)象鎖
中的阻塞狀態(tài): 當(dāng)線程運(yùn)行時(shí),試圖獲得某個(gè)對(duì)象的同步鎖時(shí)宫峦,如果該對(duì)象的同步鎖已經(jīng)被其他的線程占用玫鸟,JVM就會(huì)把這個(gè)線程放到這個(gè)對(duì)象的瑣池中屎飘。
3) 其它的阻塞狀態(tài): 當(dāng)前線程執(zhí)行了sleep()方法,或者調(diào)用了其它線程的join()方法檐盟,或者發(fā)出了I/O請(qǐng)求時(shí)肮雨,就會(huì)進(jìn)入這個(gè)狀態(tài)中。
Waiting, Time_waiting
5. 死亡狀態(tài)(Terminated): 當(dāng)線程退出run()方法陌宿,就進(jìn)入死亡狀態(tài),該線程結(jié)束了生命周期舶得。要么正常退出爽蝴、要么遇到異常退出蝎亚。
如果希望明確地讓一個(gè)線程給另外一個(gè)線程運(yùn)行的機(jī)會(huì),可以采取以下的辦法
1躺彬、調(diào)整線程的優(yōu)先級(jí)梅惯。
2、讓處于運(yùn)行狀態(tài)的線程調(diào)用Thread.sleep()方法她君,或者調(diào)用Thread.yield()方法缔刹,或者調(diào)用另一個(gè)線程的join()方法魄梯。
線程中斷
線程中斷是重要的線程協(xié)作機(jī)制宾符。如果在循環(huán)體中魏烫,出現(xiàn)類似wait()或者sleep()的操作,則只能通過中斷來識(shí)別稀蟋。
競態(tài)條件
當(dāng)多個(gè)線程同時(shí)修改同一數(shù)據(jù)對(duì)象時(shí)呐赡,可能會(huì)產(chǎn)生不正確的結(jié)果,這時(shí)候就存在一個(gè)競爭條件(race condition)萌狂。
如果首先要執(zhí)行的程序競爭失敗排到了后面執(zhí)行茫藏,那么整個(gè)程序就會(huì)出現(xiàn)不確定的bugs。這種bugs很難發(fā)現(xiàn)而且會(huì)重復(fù)出現(xiàn)凉当,因?yàn)榫€程間是隨機(jī)競爭售葡。
線程通信
Java.lang.Object類中提供兩個(gè)用于線程通信的方法
1、wait(): 線程釋放對(duì)象的鎖泊窘,JVM會(huì)把該線程放到對(duì)象的等待池中像寒。該線程等待其它線程喚醒诺祸。
2、notify(): 執(zhí)行該方法的線程喚醒在對(duì)象的等待池中等待的一個(gè)線程憔鬼,JVM從對(duì)象的等待池中隨機(jī)選擇一個(gè)線程胃夏,把它轉(zhuǎn)到對(duì)象的鎖池中仰禀。
線程睡眠:當(dāng)線程在運(yùn)行中執(zhí)行了sleep()方法時(shí),會(huì)放棄CPU饺蚊,轉(zhuǎn)到阻塞狀態(tài)悬嗓,不會(huì)釋放它所持有的鎖包竹。
等待其它線程的結(jié)束:調(diào)用另一個(gè)線程的join()方法籍凝,當(dāng)前運(yùn)行的線程將轉(zhuǎn)到阻塞狀態(tài)苗缩,直到另一個(gè)線程運(yùn)行結(jié)束挤渐,它才恢復(fù)運(yùn)行。
join與synchronized的區(qū)別是:join在內(nèi)部使用wait()方法進(jìn)行等待得问,而synchronized關(guān)鍵字使用的是“對(duì)象監(jiān)視器”做為同步软免。
多線程使用建議
- 起個(gè)有意義的名字
方便debug或追蹤膏萧。 - 避免鎖定,縮小同步的范圍
鎖花費(fèi)的代價(jià)高昂且上下文切換耗費(fèi)時(shí)間空間蝌蹂,試試最低限度的使用同步和鎖曹锨,縮小臨界區(qū)。因此相對(duì)于同步方法更喜歡同步塊齐鲤,擁有對(duì)鎖的絕對(duì)控制權(quán)给郊。 - 多用同步類捧灰,少用wait 和 notify
CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 這些同步類簡化了編碼操作,而用wait和notify很難實(shí)現(xiàn)對(duì)復(fù)雜控制流的控制吩屹。 - 多用并發(fā)集合,少用同步集合
并發(fā)集合比同步集合的可擴(kuò)展性更好免绿。