Java死鎖的產(chǎn)生原因及解決方法總結(jié)
一. 死鎖
1. 概念
Java中的死鎖是指多個線程同時占用一些共享資源且彼此相互等待,從而導(dǎo)致所有的線程都被阻塞,不能繼續(xù)執(zhí)行程序的情況。這就好比在一個十字路口,沒有交警也沒有紅綠燈指揮通行,所有的車輛都占據(jù)道路且互相等待對方讓出路權(quán),此時就很容易造成道路堵死,這其實就是道路的“死鎖”。如下圖所示:
2. 死鎖案例
雖然我們現(xiàn)在已經(jīng)知道了死鎖的概念,但具體什么時候會產(chǎn)生死鎖,相信很多小伙伴肯定還是弄不不清楚。所以接下來就給大家設(shè)計一個會產(chǎn)生死鎖的代碼案例,如下所示:
/** * @author 一一哥Sun * @company 千鋒教育 */ public class Demo21 { // 定義2個鎖定的對象 private Object lock1 = new Object(); private Object lock2 = new Object(); public void method1() { // 鎖定對象1 synchronized (lock1) { System.out.println("Method 1: 獲取對象lock1的鎖"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 鎖定對象2 synchronized (lock2) { System.out.println("Method 1: 獲取對象lock2的鎖"); } } } public void method2() { // 鎖定對象2 synchronized (lock2) { System.out.println("Method 2: 獲取對象lock2的鎖"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 鎖定對象1 synchronized (lock1) { System.out.println("Method 2: 獲取對象lock1的鎖"); } } } public static void main(String[] args) { final Demo21 example = new Demo21(); Thread thread1 = new Thread(new Runnable() { public void run() { example.method1(); } }); Thread thread2 = new Thread(new Runnable() { public void run() { example.method2(); } }); //開啟線程 thread1.start(); thread2.start(); } }
在上面的案例中,定義了兩個方法method1和method2,這兩個方法分別占用了lock1和lock2兩個鎖,并且在執(zhí)行過程中會互相等待對方的鎖,從而形成了死鎖。如果我們運行該程序,就會看到兩個線程互相等待對方釋放自己占用的鎖,這最終會導(dǎo)致所有的線程都被阻塞。上述案例中死鎖的產(chǎn)生原因,如下圖所示:
根據(jù)上面的案例,你可以總結(jié)出會導(dǎo)致死鎖的條件嗎?我們繼續(xù)往下看。
3. 產(chǎn)生條件
其實一個Java程序要想產(chǎn)生死鎖,也并不是那么容易,只有同時滿足以下條件才行:
互斥條件:多個線程需同時訪問一個共享資源,但每次只能有一個線程訪問該資源;
請求和保持條件:一個線程在持有一個資源的同時,還想請求另一個資源;
不可剝奪條件:已經(jīng)分配的資源不能被其他線程剝奪;
循環(huán)(環(huán)路)等待條件:多個線程形成了一個循環(huán)等待資源的鏈路,例如線程A等待線程B釋放自己所占用的資源,線程B等待線程C釋放自己所占用的資源,而線程C又等待線程A釋放自己所占用的資源。
只有同時滿足了以上條件,程序中才會產(chǎn)生死鎖。既然我們現(xiàn)在知道了死鎖的產(chǎn)生條件,那又該怎么解決呢?
4. 解決辦法
我們知道,當(dāng)出現(xiàn)死鎖時,所有的線程都會被阻塞,且不能再繼續(xù)執(zhí)行程序,所以我們必須解決死鎖。一般情況下,我們可以通過以下方式來避免線程死鎖:
避免使用多個鎖;
盡可能減少同步代碼塊的長度;
嘗試改變鎖的獲取順序,避免線程之間形成循環(huán)等待;
使用定時鎖,當(dāng)?shù)却龝r間超過一定的時間值后就自動釋放鎖。
以上就是打破死鎖條件的解決辦法,但是具體放到Java代碼中又是怎么樣的呢?接下來就把上面產(chǎn)生死鎖的代碼修改一下,解決死鎖問題。
5. 案例優(yōu)化
接下來就把上面產(chǎn)生死鎖的案例優(yōu)化一下,解決掉案例中的死鎖,代碼如下:
public class Demo22 { // 定義2個鎖定的對象 private Object lock1 = new Object(); private Object lock2 = new Object(); public void method1() { //鎖定對象 synchronized(lock1) { System.out.println("Method 1: 獲取對象鎖lock 1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(lock2) { System.out.println("Method 1: 獲取對象鎖lock 2"); } } } public void method2() { synchronized(lock1) { System.out.println("Method 2: 獲取對象鎖lock 1"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } synchronized(lock2) { System.out.println("Method 2: 獲取對象鎖lock 2"); } } } public static void main(String[] args) { final Demo22 demo = new Demo22(); //定義兩個線程 Thread thread1 = new Thread(new Runnable() { @Override public void run() { demo.method1(); } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { demo.method2(); } }); //開啟線程 thread1.start(); thread2.start(); } }
上面的這個案例與之前的案例代碼幾乎一樣,但與之不同的是,本案例中的方法method1和method2,都是先占用lock1鎖,再占用lock2鎖,這樣就避免了死鎖的發(fā)生,因為這兩個方法占用鎖的順序是一致的。所以我們在編寫多線程代碼時,需要特別注意線程死鎖的問題,避免影響程序的正常執(zhí)行。
二. 結(jié)語
至此,小編就把Java中的死鎖給大家講解完畢了,現(xiàn)在你明白了嗎?我們在面試時經(jīng)常會有面試官考察死鎖相關(guān)的內(nèi)容,比如死鎖是怎么產(chǎn)生的?如何避免死鎖?所以今天的內(nèi)容很重要,請各位一定要牢牢掌握哦。
以上就是Java死鎖的產(chǎn)生原因及解決方法總結(jié)的詳細內(nèi)容,更多關(guān)于Java死鎖的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
有關(guān)tomcat內(nèi)存溢出的完美解決方法
下面小編就為大家?guī)硪黄嘘P(guān)tomcat內(nèi)存溢出的完美解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-05-05Java中Map接口使用以及有關(guān)集合的面試知識點匯總
在java面試過程中,Map時常會被作為一個面試點來問,下面這篇文章主要給大家介紹了關(guān)于Java中Map接口使用以及有關(guān)集合的面試知識點匯總的相關(guān)資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-07-07