死鎖是一種非常不受歡迎的現(xiàn)象,對(duì)于不含數(shù)據(jù)庫(kù)的系統(tǒng)內(nèi)死鎖梢杭,我相信絕大多數(shù)程序員都能夠避免偏窝。不要出現(xiàn)同步代碼塊的嵌套一般就可以避免死鎖,但還有兩個(gè)問(wèn)題:
- 我能否確定程序中同步代碼塊沒(méi)有嵌套梁钾?
- 業(yè)務(wù)需求就要求我必須要鎖多個(gè)對(duì)象怎么辦绳泉?
對(duì)于第一個(gè)問(wèn)題,有的人可能覺(jué)得很簡(jiǎn)單姆泻,我自己寫(xiě)的代碼零酪,我還不知道嗎?正常情況下是這樣的的拇勃,但如果你使用了JDK中的一些線程安全對(duì)象四苇,那就不一定了,比如StringBuffer方咆、BlockingQueue等等蛔琅,這些類里自帶同步代碼,一旦處理不好死鎖會(huì)比自己寫(xiě)的更難查找峻呛。
對(duì)于第二個(gè)問(wèn)題罗售,如果我們必須要鎖A和B兩個(gè)對(duì)象,我們需要明確是不是在所有情況下钩述,AB都是一起鎖的寨躁,還是說(shuō)有的時(shí)候是一起鎖,有的時(shí)候是只鎖A或只鎖B牙勘。如果都是一起鎖的职恳,那么我們只鎖一個(gè)其實(shí)也是一樣的,當(dāng)然你有可能不放心方面,就是想鎖兩個(gè)放钦,那么只需要保證所有代碼鎖的順序是一樣的,要么都是先鎖A再鎖B恭金,不能有的這樣有的那樣(這就是常見(jiàn)于各種教科書(shū)的死鎖案例)操禀。那么好像還有一個(gè)問(wèn)題,就是有時(shí)鎖一個(gè)横腿,有時(shí)鎖兩個(gè)颓屑,解決方案就是斤寂,不管你需不需要,都鎖上揪惦,那么這種情況基本上就跟上一種情況一樣了(例外情況見(jiàn)文末補(bǔ)充說(shuō)明)遍搞。
面對(duì)不含數(shù)據(jù)庫(kù)的系統(tǒng)內(nèi)死鎖,掌握到上面的程度應(yīng)該90%都可以避免或解決了器腋。但如果問(wèn)題再加上數(shù)據(jù)庫(kù)溪猿,那就有點(diǎn)復(fù)雜了。比如纫塌,一個(gè)循環(huán)中的同步代碼塊中的數(shù)據(jù)庫(kù)操作會(huì)鎖數(shù)據(jù)庫(kù)表诊县,而代碼塊內(nèi)部并沒(méi)有提交事務(wù),那么就會(huì)造成死鎖护戳。大致代碼如下:
public void method () {
while (flag) {
synchronized (obj) { // 加鎖1
// 導(dǎo)致鎖表TABLE_1的數(shù)據(jù)庫(kù)操作 // 加鎖2
} // 釋放1
}
// commit // 釋放2
}
這段代碼甚至常常會(huì)逃過(guò)一些老程序員的法眼翎冲,因?yàn)檎б豢粗绘i一個(gè)對(duì)象,應(yīng)該沒(méi)什么問(wèn)題媳荒,但問(wèn)題就出來(lái)釋放鎖1的同時(shí)沒(méi)有釋放鎖2抗悍,而一旦釋放了鎖1,就可以被別的線程獲得鎖钳枕,從而造成死鎖缴渊。這就告訴我們一個(gè)原則:
- 加鎖與釋放應(yīng)該像棧一樣后加先釋
其實(shí)這也不難理解,就像括號(hào)一樣鱼炒,“({)}”這樣寫(xiě)肯定是不行的衔沼,必須要“({})”這樣。現(xiàn)在要避免和解決這類問(wèn)題,最關(guān)鍵的就是:什么樣的操作會(huì)造成數(shù)據(jù)庫(kù)的鎖?
要完整地回答這個(gè)問(wèn)題哎壳,就得針對(duì)各種不同的數(shù)據(jù)庫(kù)再做一番研究了曲楚,就拿ORACLE來(lái)說(shuō)潮改,行鎖、表鎖、共享鎖、排他鎖......光概念可能就要解釋好久搓劫。不過(guò)大致說(shuō)來(lái),無(wú)論什么數(shù)據(jù)庫(kù)混巧,DQL一般不會(huì)加鎖枪向,DML一般會(huì)造成行鎖,但如果表上有索引咧党,那就要特別注意秘蛔,因?yàn)檫@時(shí)DML可能造成表鎖,如果不確定后果,最好試一下缠犀。
補(bǔ)充一點(diǎn)数苫,java里面如果用Lock接口控制線程同步聪舒,那么是會(huì)出現(xiàn)“({)}”這種情況的 辨液,而一旦這樣寫(xiě),雖然不是必然導(dǎo)致死鎖箱残,但是離死鎖也不遠(yuǎn)了滔迈。