一、什么是死鎖
死鎖不僅在個(gè)人學(xué)習(xí)中,甚至在開(kāi)發(fā)中也并不常見(jiàn)。但是一旦出現(xiàn)死鎖,后果將非常嚴(yán)重。
首先什么是死鎖呢?打個(gè)比方,就好像有兩個(gè)人打架,互相限制住了(鎖住,抱住)彼此一樣,互相動(dòng)彈不得,而且互相歐氣,你不松手我就不松手。好了誰(shuí)也動(dòng)彈不得。
在多線程的環(huán)境下,勢(shì)必會(huì)對(duì)資源進(jìn)行搶奪。當(dāng)兩個(gè)線程鎖住了當(dāng)前資源,但都需要對(duì)方的資源才能進(jìn)行下一步操作,這個(gè)時(shí)候兩方就會(huì)一直等待對(duì)方的資源釋放。這就形成了死鎖。這些永遠(yuǎn)在互相等待的進(jìn)程稱(chēng)為死鎖進(jìn)程。
那么我們來(lái)總結(jié)一下死鎖產(chǎn)生的條件:
互斥:資源的鎖是排他性的,加鎖期間只能有一個(gè)線程擁有該資源。其他線程只能等待鎖釋放才能?chē)L試獲取該資源。
請(qǐng)求和保持:當(dāng)前線程已經(jīng)擁有至少一個(gè)資源,但其同時(shí)又發(fā)出新的資源請(qǐng)求,而被請(qǐng)求的資源被其他線程擁有。此時(shí)進(jìn)入保持當(dāng)前資源并等待下個(gè)資源的狀態(tài)。
不剝奪:線程已擁有的資源,只能由自己釋放,不能被其他線程剝奪。
循環(huán)等待:是指有多個(gè)線程互相的請(qǐng)求對(duì)方的資源,但同時(shí)擁有對(duì)方下一步所需的資源。形成一種循環(huán),類(lèi)似2)請(qǐng)求和保持。但此處指多個(gè)線程的關(guān)系。并不是指單個(gè)線程一直在循環(huán)中等待。
什么?還是不理解?那我們直接上代碼,動(dòng)手寫(xiě)一個(gè)死鎖。
二、寫(xiě)一個(gè)死鎖
根據(jù)條件,我們讓兩個(gè)線程互相請(qǐng)求保持。
/**
* 模擬死鎖場(chǎng)景
*/
public class DeadLockDemo implements Runnable{
public static int flag = 1;
//static 變量是 類(lèi)對(duì)象共享的
static Object o1 = new Object();
static Object o2 = new Object();
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":此時(shí) flag = " + flag);
if(flag == 1){
synchronized (o1){
try {
System.out.println("我是" + Thread.currentThread().getName() + "鎖住 o1");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "醒來(lái)->準(zhǔn)備獲取 o2");
}catch (Exception e){
e.printStackTrace();
}
synchronized (o2){
System.out.println(Thread.currentThread().getName() + "拿到 o2");//第24行
}
}
}
if(flag == 0){
synchronized (o2){
try {
System.out.println("我是" + Thread.currentThread().getName() + "鎖住 o2");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "醒來(lái)->準(zhǔn)備獲取 o1");
}catch (Exception e){
e.printStackTrace();
}
synchronized (o1){
System.out.println(Thread.currentThread().getName() + "拿到 o1");//第38行
}
}
}
}
public static void main(String args[]){
DeadLockDemo t1 = new DeadLockDemo();
DeadLockDemo t2 = new DeadLockDemo();
t1.flag = 1;
new Thread(t1).start();
//讓main線程休眠1秒鐘,保證t2開(kāi)啟鎖住o2.進(jìn)入死鎖
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.flag = 0;
new Thread(t2).start();
}
代碼中,
t1創(chuàng)建,t1先拿到o1的鎖,開(kāi)始休眠3秒。然后
t2線程創(chuàng)建,t2拿到o2的鎖,開(kāi)始休眠3秒。然后
t1先醒來(lái),準(zhǔn)備拿o2的鎖,發(fā)現(xiàn)o2已經(jīng)加鎖,只能等待o2的鎖釋放。
t2后醒來(lái),準(zhǔn)備拿o1的鎖,發(fā)現(xiàn)o1已經(jīng)加鎖,只能等待o1的鎖釋放。
t1,t2形成死鎖。
我們查看運(yùn)行狀態(tài)

三、發(fā)現(xiàn)排查死鎖情況
我們利用jdk提供的工具定位死鎖問(wèn)題:
jps顯示所有當(dāng)前Java虛擬機(jī)進(jìn)程名及pid.
jstack打印進(jìn)程堆棧信息。

列出所有java進(jìn)程。
我們檢查一下DeadLockDemo,為什么這個(gè)線程不退棧。
jstack 11170

我們直接翻到最后:已經(jīng)檢測(cè)出了一個(gè)java級(jí)別死鎖。其中兩個(gè)線程分別卡在了代碼第27行和第41行。檢查我們代碼的對(duì)應(yīng)位置,即可排查錯(cuò)誤。此處我們是第二個(gè)鎖始終拿不到,所以死鎖了。
四、解決辦法
死鎖一旦發(fā)生,我們就無(wú)法解決了。所以我們只能避免死鎖的發(fā)生。
既然死鎖需要滿足四種條件,那我們就從條件下手,只要打破任意規(guī)則即可。
(互斥)盡量少用互斥鎖,能加讀鎖,不加寫(xiě)鎖。當(dāng)然這條無(wú)法避免。
(請(qǐng)求和保持)采用資源靜態(tài)分配策略(進(jìn)程資源靜態(tài)分配方式是指一個(gè)進(jìn)程在建立時(shí)就分配了它需要的全部資源).我們盡量不讓線程同時(shí)去請(qǐng)求多個(gè)鎖,或者在擁有一個(gè)鎖又請(qǐng)求不到下個(gè)鎖時(shí),不保持等待,先釋放資源等待一段時(shí)間在重新請(qǐng)求。
(不剝奪)允許進(jìn)程剝奪使用其他進(jìn)程占有的資源。優(yōu)先級(jí)。
(循環(huán)等待)盡量調(diào)整獲得鎖的順序,不發(fā)生嵌套資源請(qǐng)求。加入超時(shí)。