Java多線程中的死鎖詳解
前言
死鎖(Deadlock)是指兩個(gè)或多個(gè)線程在執(zhí)行過程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象
如果不提前預(yù)防或外界干擾,這些線程將無法執(zhí)行下去。
因此,本篇博文講解造成死鎖的原因以及解決方案。
1. 造成死鎖的原因
死鎖是多個(gè)線程在相互等待對(duì)方所占有的鎖才能繼續(xù)執(zhí)行下去,造成線程都被阻塞都無法執(zhí)行下去,也釋放不了線程自己的鎖資源,造成僵局。
舉例說明:
我和朋友去拉面館吃面,我先拿起了辣椒他拿起了醋。此時(shí)他想要辣椒、我想要醋,但我倆誰也不讓誰。
我對(duì)他說:你先把辣椒給我,我用完了就把醋給你。他對(duì)我說:你先把醋給我,我用完了就把辣椒給你。
我和他也不讓誰,最后導(dǎo)致死鎖。在上述例子中,辣椒和醋就是兩個(gè)鎖,我和他就是兩個(gè)線程。關(guān)于死鎖會(huì)引申的一個(gè)哲學(xué)家就餐問題。
2. 哲學(xué)家就餐問題
哲學(xué)家就餐問題是一個(gè)經(jīng)典的并發(fā)編程問題,它描述的是五個(gè)哲學(xué)家圍坐在一張圓形餐桌旁,每個(gè)哲學(xué)家面前有一碗飯和一只筷子,碗和筷子都是共享資源。
哲學(xué)家需要用一雙筷子來吃飯,但是只有相鄰的兩個(gè)哲學(xué)家之間有一只筷子,因此他們必須在餐桌上競(jìng)爭(zhēng)筷子,才能拿到一雙筷子吃飯。
這個(gè)問題會(huì)引發(fā)死鎖(Deadlock)和饑餓(Starvation)等并發(fā)編程中的經(jīng)典問題。
如果所有哲學(xué)家同時(shí)都想獲取餐具,但每個(gè)哲學(xué)家只能等待旁邊的哲學(xué)家放下筷子才能獲取餐具,這可能會(huì)導(dǎo)致死鎖。
如果所有哲學(xué)家只有在其他人都吃到飯才能拿到自己的餐具,那么有些哲學(xué)家可能會(huì)因?yàn)榈却龝r(shí)間太長(zhǎng)而饑餓。
代碼案例:
有兩個(gè)哲學(xué)家在進(jìn)行就餐,哲學(xué)家1獲取到筷子1后繼續(xù)嘗試獲取筷子2,哲學(xué)家2獲取到筷子2后繼續(xù)嘗試獲取筷子1,造成了死鎖狀態(tài)。
public class DeadlockDemo { public static void main(String[] args) { Object obj1 = new Object();//筷子1 Object obj2 = new Object();//筷子2 // 線程1獲取obj1,然后等待obj2 Thread thread1 = new Thread(() -> { synchronized (obj1) { System.out.println(Thread.currentThread().getName() + " 獲取 obj1鎖"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2) { System.out.println(Thread.currentThread().getName() + " 獲取 obj2鎖"); } } }, "哲學(xué)家 1"); // 線程2獲取obj2,然后等待obj1 Thread thread2 = new Thread(() -> { synchronized (obj2) { System.out.println(Thread.currentThread().getName() + " 獲取 obj2鎖"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1) { System.out.println(Thread.currentThread().getName() + " 獲取 obj1鎖"); } } }, "哲學(xué)家 2"); t1.start(); t2.start(); } }
運(yùn)行后打印:
上述代碼,中 thread1 是哲學(xué)家1,thread2 是哲學(xué)家2,obj1 鎖和 obj2 鎖分別代表兩只筷子。
哲學(xué)家1 占用了 obj1 鎖,哲學(xué)家2 占用了 obj2 鎖。
此時(shí) 哲學(xué)家1 在擁有 obj1 時(shí)獲取 obj2 是獲取不到的,哲學(xué)家2 在擁有 obj2 時(shí)獲取 obj1 也是獲取不到的。
這樣就會(huì)造成死鎖。也就是本篇文章開頭所說的,一個(gè)線程處于永久獲取到鎖的狀態(tài),其他線程無限阻塞等待,這就造成了死鎖。
2.1 造成死鎖的原因
- 互斥:一個(gè)資源只能被一個(gè)進(jìn)程獨(dú)占,即該資源只能被一個(gè)進(jìn)程占有或使用。
- 請(qǐng)求和保持:進(jìn)程已經(jīng)保持了至少一個(gè)資源,并還在嘗試獲取其他進(jìn)程保持的資源。
- 不可搶占:某個(gè)資源只能由占有它的線程釋放,不能被其他線程強(qiáng)制占有或搶占。
- 循環(huán)等待:假設(shè)一個(gè)隊(duì)列有{t1,t2,t3}這三個(gè)線程,t1 等待 t2 所占資源,t2 等待 t3 所占資源,t3 等待 t1 所占資源,這樣就會(huì)造成循環(huán)等待。
2.2 解決死鎖問題
解決死鎖問題,最好的方式就是預(yù)防死鎖。我們把鎖按照順序進(jìn)行編號(hào),并且約定每個(gè)線程在獲取多把鎖的時(shí)候必須先獲取編號(hào)小的,后獲取編號(hào)大的。這樣就不會(huì)形成死鎖了。
因?yàn)?,最先獲取編號(hào)小鎖的線程會(huì)優(yōu)先完成相應(yīng)任務(wù)。輪到其他線程再獲取小編號(hào)鎖時(shí),鎖就處于空閑狀態(tài)了。
代碼案例:
public class DeadlockDemo { public static void main(String[] args) { Object obj1 = new Object();//筷子1 Object obj2 = new Object();//筷子2 Thread t1 = new Thread(() -> { synchronized (obj1) { System.out.println(Thread.currentThread().getName() + " 獲取 obj1鎖"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2) { System.out.println(Thread.currentThread().getName() + " 獲取 obj2鎖"); } } }, "哲學(xué)家 1"); Thread t2 = new Thread(() -> { synchronized (obj1) { System.out.println(Thread.currentThread().getName() + " 獲取 obj2鎖"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2) { System.out.println(Thread.currentThread().getName() + " 獲取 obj1鎖"); } } }, "哲學(xué)家 2"); t1.start(); t2.start(); } }
運(yùn)行后打?。?/p>
在上述代碼中,我們創(chuàng)建了兩個(gè)鎖對(duì)象 obj1 和 obj2 分別代表兩只筷子,并創(chuàng)建兩個(gè)線程 thread1 和 thread 2 分別代表兩個(gè)哲學(xué)家。
當(dāng) 哲學(xué)家1 首先獲取到 obj1 后,由于 obj2 并未線程占用,則立馬獲取 obj2 ,獲得了兩雙筷子,就餐后釋放 obj1 和 obj2。
這時(shí) 哲學(xué)家2 嘗試獲取 obj2 和 obj1 時(shí),發(fā)現(xiàn)兩雙筷子都沒人使用,直接開始就餐。
總結(jié)
什么是死鎖?
當(dāng)多個(gè)線程并發(fā)后,線程在占有鎖資源的情況下還嘗試獲取別的線程所占有的鎖資源。
這樣就會(huì)操作線程永遠(yuǎn)獲取不到其他鎖資源,也不能釋放本身鎖資源。
從而形成的一種僵局,這就是死鎖。
造成死鎖的原因是什么?
造成死鎖的原因有,互斥、請(qǐng)求和保持、不可搶占、循環(huán)等待。(文章中有講解)
死鎖的解決方案
我們可以對(duì)多個(gè)鎖按照順序進(jìn)行編號(hào),并約定每個(gè)線程在使用多把鎖時(shí)優(yōu)先使用最小編號(hào)的鎖,這樣就能保證其他線程再獲取小編號(hào)鎖時(shí),鎖資源已得到釋放。
到此這篇關(guān)于Java多線程中的死鎖詳解的文章就介紹到這了,更多相關(guān)Java多線程死鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解IDEA使用Maven項(xiàng)目不能加入本地Jar包的解決方法
這篇文章主要介紹了詳解IDEA使用Maven項(xiàng)目不能加入本地Jar包的解決方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-08-08解析Spring RestTemplate必須搭配MultiValueMap的理由
本文給大家介紹Spring RestTemplate必須搭配MultiValueMap的理由,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),需要的朋友參考下吧2021-11-11升級(jí)dubbo2.7.4.1版本平滑遷移到注冊(cè)中心nacos
這篇文章主要為大家介紹了2.7.4.1的dubbo平滑遷移到注冊(cè)中心nacos的兩種版本升級(jí)方案,以及為什要升級(jí),有需要的朋友可以借鑒參考下,希望能夠有所幫助2022-02-02Java Properties簡(jiǎn)介_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
Java中有個(gè)比較重要的類Properties(Java.util.Properties),主要用于讀取Java的配置文件,各種語言都有自己所支持的配置文件,配置文件中很多變量是經(jīng)常改變的,這樣做也是為了方便用戶,讓用戶能夠脫離程序本身去修改相關(guān)的變量設(shè)置2017-05-05Java如何通過反射機(jī)制獲取數(shù)據(jù)類對(duì)象的屬性及方法
文章介紹了如何使用Java反射機(jī)制獲取類對(duì)象的所有屬性及其對(duì)應(yīng)的get、set方法,以及如何通過反射機(jī)制實(shí)現(xiàn)類對(duì)象的實(shí)例化,感興趣的朋友跟隨小編一起看看吧2025-01-01Spring如何替換掉默認(rèn)common-logging.jar
這篇文章主要介紹了Spring如何替換掉默認(rèn)common-logging.jar,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05java實(shí)現(xiàn)單鏈表倒轉(zhuǎn)的方法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)單鏈表倒轉(zhuǎn)的方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05Spring?Security前后分離校驗(yàn)token的實(shí)現(xiàn)方法
這篇文章主要介紹了Spring?Security前后分離校驗(yàn)token的方法,本次token生成采取jwt的方式,通過引入jwt依賴文件配置token管理器,對(duì)Spring?Security校驗(yàn)token相關(guān)知識(shí)感興趣的朋友一起看看吧2022-02-02DUBBO 日志過濾器,輸出dubbo 接口調(diào)用入?yún)?、出參等信?最新推薦)
這篇文章主要介紹了DUBBO 日志過濾器,輸出dubbo 接口調(diào)用入?yún)?、出參等信?首先自定義一個(gè)過濾器?DubboLoggerFilter.java,本文結(jié)合示例代碼給大家講解的非常詳細(xì),需要的朋友可以參考下2022-12-12Spring獲取當(dāng)前類在容器中的beanname實(shí)現(xiàn)思路
這篇文章主要介紹了Spring獲取當(dāng)前類在容器中的beanname,實(shí)現(xiàn)思路只需繼承BeanNameAware接口,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-07-07