一種死鎖的情況……

前言
在多線程編程的時候,為了確保同一時間數(shù)據(jù)狀態(tài)的唯一性,我們經(jīng)常會用到鎖(lock),它用起來很方便也很簡單,但是在某些特定的應(yīng)用場景之下,它會給你帶來很多困惑,,比如死鎖的情況,今天我們就來通過一段簡單的代碼,來看下什么是死鎖,以及如何避免死鎖。
死鎖
我們通過一段代碼來模擬下死鎖的情況:
public?class?Example?{
????private?static?String?a?=?"a";
????private?static?String?b?=?"b";
????public?static?void?main(String[]?args)?{
????????new?Example().deadLock();
????}
????private?void?deadLock()?{
????????Thread?t1?=?new?Thread(()?->?{
????????????synchronized?(a)?{
????????????????try?{
????????????????????Thread.currentThread().sleep(2000);
????????????????}?catch?(Exception?e)?{
????????????????????e.printStackTrace();
????????????????}
????????????????synchronized?(b)?{
????????????????????System.out.println("b1");
????????????????}
????????????}
????????});
????????Thread?t2?=?new?Thread(()?->?{
????????????synchronized?(b)?{
????????????????synchronized?(a)?{
????????????????????System.out.println("a2");
????????????????}
????????????}
????????});
????????t1.start();
????????t2.start();
????}
}
上面的代碼中,我們定義了兩個static變量,在deadLock方法內(nèi),分別定義了兩個線程,在第一個線程內(nèi),我們通過synchronized關(guān)鍵字分別對變量a和b加鎖,b鎖位于a鎖內(nèi)部,在獲取b資源前,先休眠2秒;在第二個線程內(nèi),我們也是通過synchronized關(guān)鍵字分別對b和a進行加鎖,a鎖位于b鎖內(nèi),但是沒有睡眠。
上面兩個線程,運行順序是這樣的:
如果是t1先啟動,t1線程啟動后會先對a資源加鎖,之后t1休眠2秒,在在線程t1啟動到休眠結(jié)束這段時間中,t2線程啟動并對b資源加鎖,并嘗試獲取a的鎖,但由于a已經(jīng)被t1加鎖且未釋放鎖,這時候t2開始等待t1釋放資源,一直到t1休眠結(jié)束也未獲取到資源,這時候t1開始嘗試獲取b資源的鎖,但由于b資源被t2占用,所以t1也必須等待,最終的結(jié)果是兩個線程都在等待對方釋放資源,兩個線程都被阻塞,導致死鎖。
t2先啟動的情況,實際測試中沒法復現(xiàn),但如果把t2放在t1前面start,那是不會導致死鎖的,t2執(zhí)行完很快就釋放資源了,所以不會導致線程阻塞,但如果加了睡眠時間,就和t1先啟動的情況一樣了。
不喜歡看文字的小伙伴,可以看這個時序圖,很直觀地體現(xiàn)了死鎖的情況:

如何避免死鎖
既然知道了導致死鎖的原因,我們應(yīng)該如何避免這種情況出現(xiàn)呢?
第一個解決方法——戒貪,簡單來說,就是盡量避免一個線程獲取多個鎖的情況,如果只是對一個資源加鎖的話,那自然是不會導致死鎖情況出現(xiàn)的;
第二解決方法,就是引入超時機制,也就是采用定時鎖,使用lock.tryLock(timeout)替換synchronized。
對于,數(shù)據(jù)庫鎖,加鎖和解鎖必須在同一個數(shù)據(jù)庫連接中進行,否則會出現(xiàn)解鎖失敗的情況。
總結(jié)
鎖是多線程編程中一個特別有效的工具,很方便,也解決了資源共享的數(shù)據(jù)同步問題,但如何用好這個工具就是門藝術(shù)了,總之一句話就是要多學習,多實踐,不斷試錯,除此之外,沒有其他的捷徑了。
多線程這塊的知識,想要學好學精,不僅要了解jvm的相關(guān)知識,還要熟悉處理器層面的知識,山高路遠,還得加油呀!
好了,今天就到這里吧
- END -