Java死鎖的產(chǎn)生原因及解決方法總結(jié)
一. 死鎖
1. 概念
Java中的死鎖是指多個(gè)線程同時(shí)占用一些共享資源且彼此相互等待,從而導(dǎo)致所有的線程都被阻塞,不能繼續(xù)執(zhí)行程序的情況。這就好比在一個(gè)十字路口,沒(méi)有交警也沒(méi)有紅綠燈指揮通行,所有的車輛都占據(jù)道路且互相等待對(duì)方讓出路權(quán),此時(shí)就很容易造成道路堵死,這其實(shí)就是道路的“死鎖”。如下圖所示:

2. 死鎖案例
雖然我們現(xiàn)在已經(jīng)知道了死鎖的概念,但具體什么時(shí)候會(huì)產(chǎn)生死鎖,相信很多小伙伴肯定還是弄不不清楚。所以接下來(lái)就給大家設(shè)計(jì)一個(gè)會(huì)產(chǎn)生死鎖的代碼案例,如下所示:
/**
* @author 一一哥Sun
* @company 千鋒教育
*/
public class Demo21 {
// 定義2個(gè)鎖定的對(duì)象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
// 鎖定對(duì)象1
synchronized (lock1) {
System.out.println("Method 1: 獲取對(duì)象lock1的鎖");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 鎖定對(duì)象2
synchronized (lock2) {
System.out.println("Method 1: 獲取對(duì)象lock2的鎖");
}
}
}
public void method2() {
// 鎖定對(duì)象2
synchronized (lock2) {
System.out.println("Method 2: 獲取對(duì)象lock2的鎖");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 鎖定對(duì)象1
synchronized (lock1) {
System.out.println("Method 2: 獲取對(duì)象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();
}
}
在上面的案例中,定義了兩個(gè)方法method1和method2,這兩個(gè)方法分別占用了lock1和lock2兩個(gè)鎖,并且在執(zhí)行過(guò)程中會(huì)互相等待對(duì)方的鎖,從而形成了死鎖。如果我們運(yùn)行該程序,就會(huì)看到兩個(gè)線程互相等待對(duì)方釋放自己占用的鎖,這最終會(huì)導(dǎo)致所有的線程都被阻塞。上述案例中死鎖的產(chǎn)生原因,如下圖所示:

根據(jù)上面的案例,你可以總結(jié)出會(huì)導(dǎo)致死鎖的條件嗎?我們繼續(xù)往下看。
3. 產(chǎn)生條件
其實(shí)一個(gè)Java程序要想產(chǎn)生死鎖,也并不是那么容易,只有同時(shí)滿足以下條件才行:
互斥條件:多個(gè)線程需同時(shí)訪問(wèn)一個(gè)共享資源,但每次只能有一個(gè)線程訪問(wèn)該資源;
請(qǐng)求和保持條件:一個(gè)線程在持有一個(gè)資源的同時(shí),還想請(qǐng)求另一個(gè)資源;
不可剝奪條件:已經(jīng)分配的資源不能被其他線程剝奪;
循環(huán)(環(huán)路)等待條件:多個(gè)線程形成了一個(gè)循環(huán)等待資源的鏈路,例如線程A等待線程B釋放自己所占用的資源,線程B等待線程C釋放自己所占用的資源,而線程C又等待線程A釋放自己所占用的資源。

只有同時(shí)滿足了以上條件,程序中才會(huì)產(chǎn)生死鎖。既然我們現(xiàn)在知道了死鎖的產(chǎn)生條件,那又該怎么解決呢?
4. 解決辦法
我們知道,當(dāng)出現(xiàn)死鎖時(shí),所有的線程都會(huì)被阻塞,且不能再繼續(xù)執(zhí)行程序,所以我們必須解決死鎖。一般情況下,我們可以通過(guò)以下方式來(lái)避免線程死鎖:
避免使用多個(gè)鎖;
盡可能減少同步代碼塊的長(zhǎng)度;
嘗試改變鎖的獲取順序,避免線程之間形成循環(huán)等待;
使用定時(shí)鎖,當(dāng)?shù)却龝r(shí)間超過(guò)一定的時(shí)間值后就自動(dòng)釋放鎖。
以上就是打破死鎖條件的解決辦法,但是具體放到Java代碼中又是怎么樣的呢?接下來(lái)就把上面產(chǎn)生死鎖的代碼修改一下,解決死鎖問(wèn)題。
5. 案例優(yōu)化
接下來(lái)就把上面產(chǎn)生死鎖的案例優(yōu)化一下,解決掉案例中的死鎖,代碼如下:
public class Demo22 {
// 定義2個(gè)鎖定的對(duì)象
private Object lock1 = new Object();
private Object lock2 = new Object();
public void method1() {
//鎖定對(duì)象
synchronized(lock1) {
System.out.println("Method 1: 獲取對(duì)象鎖lock 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("Method 1: 獲取對(duì)象鎖lock 2");
}
}
}
public void method2() {
synchronized(lock1) {
System.out.println("Method 2: 獲取對(duì)象鎖lock 1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(lock2) {
System.out.println("Method 2: 獲取對(duì)象鎖lock 2");
}
}
}
public static void main(String[] args) {
final Demo22 demo = new Demo22();
//定義兩個(gè)線程
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();
}
}
上面的這個(gè)案例與之前的案例代碼幾乎一樣,但與之不同的是,本案例中的方法method1和method2,都是先占用lock1鎖,再占用lock2鎖,這樣就避免了死鎖的發(fā)生,因?yàn)檫@兩個(gè)方法占用鎖的順序是一致的。所以我們?cè)诰帉懚嗑€程代碼時(shí),需要特別注意線程死鎖的問(wèn)題,避免影響程序的正常執(zhí)行。
二. 結(jié)語(yǔ)
至此,小編就把Java中的死鎖給大家講解完畢了,現(xiàn)在你明白了嗎?我們?cè)诿嬖嚂r(shí)經(jīng)常會(huì)有面試官考察死鎖相關(guān)的內(nèi)容,比如死鎖是怎么產(chǎn)生的?如何避免死鎖?所以今天的內(nèi)容很重要,請(qǐng)各位一定要牢牢掌握哦。
以上就是Java死鎖的產(chǎn)生原因及解決方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Java死鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一文徹底弄懂Java中MultipartFile接口和File類
MultipartFile是一個(gè)接口,我們可以理解為是Spring?給我們綁定的一個(gè)在使用文件上傳等時(shí)簡(jiǎn)便實(shí)現(xiàn)的口子,這篇文章主要給大家介紹了關(guān)于如何通過(guò)一文徹底弄懂Java中MultipartFile接口和File類的相關(guān)資料,需要的朋友可以參考下2023-11-11
淺析java并發(fā)中的Synchronized關(guān)鍵詞
這篇文章主要介紹了java并發(fā)中的Synchronized關(guān)鍵詞,本文通過(guò)思路代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-02-02
Kotlin-Coroutines中的async與await異步協(xié)程管理
這篇文章主要為大家介紹了Kotlin-Coroutines中的async與await異步協(xié)程管理,提升程序性能解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-10-10
Spring Cloud使用Feign進(jìn)行遠(yuǎn)程調(diào)用的操作指南
本文介紹了Feign作為聲明式HTTP客戶端在SpringCloud中的使用,從簡(jiǎn)介、對(duì)比RestTemplate的問(wèn)題、使用步驟,到日志配置、性能優(yōu)化和實(shí)際應(yīng)用進(jìn)行了詳細(xì)講解,包括如何通過(guò)Feign簡(jiǎn)化接口調(diào)用,以及解決啟動(dòng)時(shí)找不到FeignClient的問(wèn)題,需要的朋友可以參考下2025-02-02
Spring容器初始化擴(kuò)展點(diǎn)之ApplicationContextInitializer詳解
ApplicationContextInitializer是Spring框架提供的一個(gè)接口,用于在Spring應(yīng)用上下文刷新之前對(duì)其進(jìn)行自定義初始化,本文介紹Spring容器初始化擴(kuò)展點(diǎn)之ApplicationContextInitializer,感興趣的朋友一起看看吧2025-02-02
SpringBoot啟動(dòng)過(guò)程與自動(dòng)配置過(guò)程解讀
本文詳解SpringBoot啟動(dòng)流程及自動(dòng)配置機(jī)制,涵蓋main方法初始化、環(huán)境配置、容器啟動(dòng)等關(guān)鍵步驟,解析條件注解與spring.factories的智能配置邏輯,并提供自定義配置方法,強(qiáng)調(diào)其"約定優(yōu)于配置"的核心理念與工程化封裝特性2025-08-08
maven打包成第三方j(luò)ar包且把pom依賴包打入進(jìn)來(lái)的方法
這篇文章主要介紹了maven打包成第三方j(luò)ar包且把pom依賴包打入進(jìn)來(lái)的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11

