Java并發(fā)系列番外篇——同步機(jī)制(三)
姊妹篇《Java同步機(jī)制之synchronized》
姊妹篇《Java同步機(jī)制之volatile》
指兩個(gè)或兩個(gè)以上的進(jìn)程在執(zhí)行過程中,因爭奪資源而造成的一種互相等待的現(xiàn)象,若無外力作用份殿,它們都將無法推進(jìn)下去巫延。
先來看一則新聞:
《武器貿(mào)易條約》是聯(lián)合國為監(jiān)管八類常規(guī)武器的國際貿(mào)易制定的共同國際標(biāo)準(zhǔn),該條約于2013年4月2日在聯(lián)合國大會(huì)上通過掌呜。2013年6月3日在紐約聯(lián)合國總部開放供所有國家簽署,當(dāng)天簽署的國家超過60個(gè)。不過紧卒,美國、中國和俄羅斯這3個(gè)安理會(huì)常任理事國當(dāng)天沒有簽約诗祸。后來跑芳,最大武器生產(chǎn)和出口國美國簽署該條約,但未正式批準(zhǔn)直颅。中國和俄羅斯還未簽署這一條約博个。2019年4月26日,美國總統(tǒng)特朗普在印第安納州舉行的美國步槍協(xié)會(huì)年會(huì)上宣布功偿,《武器貿(mào)易條約》是一個(gè)“嚴(yán)重誤導(dǎo)的條約”盆佣,美國將撤銷在該條約上的簽字。特朗普將要求國會(huì)參議院停止該條約的批準(zhǔn)程序械荷,使美國退出聯(lián)合國《武器貿(mào)易條約》共耍。
其實(shí)是這樣的:
美國:我等俄羅斯先簽了我就簽;
俄羅斯:我等中國先簽我了就簽养葵;
中國:我等美國先簽了我就簽征堪;
算了,不等了关拒。因?yàn)樗梨i了佃蚜。
代碼示例
當(dāng)一個(gè)線程永遠(yuǎn)的持有一個(gè)鎖,并且其他線程線程都嘗試獲得鎖時(shí)着绊,那么它們將永遠(yuǎn)被阻塞谐算。在線程A持有鎖a并且想獲得鎖b時(shí),線程B持有鎖b并嘗試獲得a時(shí)归露,那么這兩個(gè)線程將永遠(yuǎn)的等待下去洲脂。這種情況就是最簡單的死鎖形式:
public class TestDead {
private Object a;
private Object b;
public TestDead(Object a, Object b) {
this.a = a;
this.b = b;
}
public void funOne(){
synchronized (a){
System.out.println("finOne");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
funTwo();
}
}
public void funTwo(){
synchronized (b){
System.out.println("finTwo");
try {
Thread.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
funOne();
}
}
}
TestDead testDead = new TestDead(new Object(), new Object());
new Thread(new Runnable() {
@Override
public void run() {
testDead.funOne();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
testDead.funTwo();
}
}).start();
程序的運(yùn)行結(jié)果如下:
上面的代碼會(huì)造成線程的假死,在沒有外力作用下。兩個(gè)線程會(huì)永遠(yuǎn)處于阻塞的狀態(tài)恐锦,等待一把永遠(yuǎn)不可以釋放的鎖往果。
產(chǎn)生死鎖的必要條件
- 互斥條件:指進(jìn)程對(duì)所分配到的資源進(jìn)行排它性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)進(jìn)程占用一铅。如果此時(shí)還有其它進(jìn)程請(qǐng)求資源陕贮,則請(qǐng)求者只能等待,直至占有資源的進(jìn)程用畢釋放潘飘。
- 占有且等待:指進(jìn)程已經(jīng)保持至少一個(gè)資源肮之,但又提出了新的資源請(qǐng)求,而該資源已被其它進(jìn)程占有卜录,此時(shí)請(qǐng)求進(jìn)程阻塞戈擒,但又對(duì)自己已獲得的其它資源保持不放。
- 不可強(qiáng)行占有:指進(jìn)程已獲得的資源艰毒,在未使用完之前筐高,不能被剝奪,只能在使用完時(shí)由自己釋放丑瞧。
- 循環(huán)等待條件:指在發(fā)生死鎖時(shí)凯傲,必然存在一個(gè)進(jìn)程——資源的環(huán)形鏈,即進(jìn)程集合{P0嗦篱,P1,P2幌缝,···灸促,Pn}中的P0正在等待一個(gè)P1占用的資源;P1正在等待P2占用的資源涵卵,……浴栽,Pn正在等待已被P0占用的資源。
<u>這四個(gè)條件是死鎖的必要條件轿偎,只要系統(tǒng)發(fā)生死鎖典鸡,這些條件必然成立,而只要上述條件之一不滿足坏晦,就不會(huì)發(fā)生死鎖萝玷。</u>
死鎖的避免與診斷
與許多其他并發(fā)危險(xiǎn)一樣,死鎖造成的影響很少會(huì)立刻呈現(xiàn)出來昆婿。如果一個(gè)類可能發(fā)生死鎖球碉,并不意味這每次都會(huì)發(fā)生死鎖,而是只表示有可能仓蛆。當(dāng)死鎖發(fā)生時(shí)睁冬,往往是在最糟糕的時(shí)刻——在高負(fù)載情況下。
上文中的代碼鎖產(chǎn)生的死鎖可以通過查看是否存在嵌套的鎖獲取操作
來檢查是否存在死鎖的可能看疙。但有時(shí)候獲取多個(gè)鎖的操作并不會(huì)這么明顯豆拨。
要避免出現(xiàn)死鎖的問題直奋,只需要破壞四個(gè)條件中的任何一個(gè)就可以了:
- 打破互斥條件。即允許進(jìn)程同時(shí)訪問某些資源施禾。當(dāng)然脚线,這種方法試用范圍并不廣,因?yàn)橛械馁Y源不允許并發(fā)訪問拾积,或者存在線程安全問題殉挽。
- 打破占有且等待條件⊥厍桑可以通過規(guī)定在任何情況下斯碌,一個(gè)線程獲取一個(gè)鎖之后,必須歸還了鎖之后才能請(qǐng)求另一鎖
- 打破不可搶占條件肛度。除非線程自己還鑰匙傻唾,否則線程會(huì)一直占有鑰匙,是形成不可搶占條件的原因承耿。允許進(jìn)程強(qiáng)行從占有者那里奪取某些資源冠骄。就是說,當(dāng)一個(gè)進(jìn)程已占有了某些資源加袋,它又申請(qǐng)新的資源凛辣,但不能立即被滿足時(shí),它必須釋放所占有的全部資源职烧,以后再重新申請(qǐng)扁誓。
- 打破循環(huán)等待條件。采用這種策略蚀之,即把資源事先分類編號(hào)蝗敢,按號(hào)分配,可以強(qiáng)制規(guī)定任何線程取鎖的順序足删。
其他活躍性危險(xiǎn)
盡管死鎖是最常見的活躍性危險(xiǎn)寿谴,但在并發(fā)中還存在一些其他的活躍性危險(xiǎn),包括:饑餓失受、活鎖等
饑餓:當(dāng)線程由于無法訪問它所需要的資源而不能繼續(xù)執(zhí)行時(shí)讶泰,就發(fā)生了饑餓。最常見的引發(fā)饑餓的資源就是CPU的時(shí)鐘周期拂到。
如果對(duì)線程的優(yōu)先級(jí)使用不當(dāng)峻厚,或者在持有鎖時(shí)進(jìn)行一些無法結(jié)束的操作(無限循環(huán)),就有可能產(chǎn)生饑餓谆焊,因?yàn)槠渌枰@個(gè)鎖的線程永遠(yuǎn)無法得到它惠桃。
活鎖:我為它賦予了一個(gè)充滿神話色彩的名字西西弗斯鎖
,西西弗斯神話
這是另一種形式的活躍性問題,盡管它不會(huì)阻塞線程辜王,但也不能繼續(xù)向下執(zhí)行劈狐,因?yàn)榫€程將不斷重復(fù)執(zhí)行相同的操作,而且總會(huì)失敗呐馆。
當(dāng)多個(gè)相互協(xié)作的線程都對(duì)彼此進(jìn)行響應(yīng)從而修改各自狀態(tài)肥缔,并使任何一個(gè)線程都無法執(zhí)行時(shí),就會(huì)發(fā)生活鎖汹来。
活鎖通常發(fā)生在處理事務(wù)的代碼中:如果不能成功的處理某個(gè)消息续膳,那么消息處理機(jī)制將回滾整個(gè)事務(wù),并將它重新放置在隊(duì)列的開頭收班。因此坟岔,處理事務(wù)的代碼將被反復(fù)調(diào)用,并返回相同的錯(cuò)誤結(jié)果摔桦,雖然處理消息的線程沒有被阻塞社付,但也無法執(zhí)行下去。
小結(jié)
死鎖(以及其他活躍性問題)是一個(gè)非常嚴(yán)重的問題邻耕,因?yàn)楫?dāng)它出現(xiàn)的時(shí)候鸥咖,除了中止應(yīng)用程序之外沒有其他任何方法可以修復(fù)這種故障。在設(shè)計(jì)時(shí)應(yīng)確保線程在獲取多個(gè)鎖時(shí)采用一致性的順序
兄世。