一、什么是死鎖
死鎖不僅在個人學(xué)習中擎宝,甚至在開發(fā)中也并不常見郁妈。但是一旦出現(xiàn)死鎖,后果將非常嚴重绍申。
首先什么是死鎖呢噩咪?打個比方顾彰,就好像有兩個人打架,互相限制住了(鎖住胃碾,抱住)彼此一樣涨享,互相動彈不得,而且互相歐氣仆百,你不松手我就不松手厕隧。好了誰也動彈不得。
在多線程的環(huán)境下俄周,勢必會對資源進行搶奪吁讨。當兩個線程鎖住了當前資源,但都需要對方的資源才能進行下一步操作峦朗,這個時候兩方就會一直等待對方的資源釋放建丧。這就形成了死鎖。這些永遠在互相等待的進程稱為死鎖進程波势。
那么我們來總結(jié)一下死鎖產(chǎn)生的條件:
互斥:資源的鎖是排他性的茶鹃,加鎖期間只能有一個線程擁有該資源。其他線程只能等待鎖釋放才能嘗試獲取該資源艰亮。
請求和保持:當前線程已經(jīng)擁有至少一個資源闭翩,但其同時又發(fā)出新的資源請求,而被請求的資源被其他線程擁有迄埃。此時進入保持當前資源并等待下個資源的狀態(tài)疗韵。
不剝奪:線程已擁有的資源,只能由自己釋放侄非,不能被其他線程剝奪蕉汪。
循環(huán)等待:是指有多個線程互相的請求對方的資源泵督,但同時擁有對方下一步所需的資源绽族。形成一種循環(huán),類似2)請求和保持阔籽。但此處指多個線程的關(guān)系叠赦。并不是指單個線程一直在循環(huán)中等待驹马。
什么?還是不理解除秀?那我們直接上代碼糯累,動手寫一個死鎖。
二册踩、寫一個死鎖
根據(jù)條件泳姐,我們讓兩個線程互相請求保持。
/**
* 模擬死鎖場景
*/
public class DeadLockDemo implements Runnable{
public static int flag = 1;
//static 變量是 類對象共享的
static Object o1 = new Object();
static Object o2 = new Object();
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":此時 flag = " + flag);
if(flag == 1){
synchronized (o1){
try {
System.out.println("我是" + Thread.currentThread().getName() + "鎖住 o1");
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "醒來->準備獲取 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() + "醒來->準備獲取 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開啟鎖住o2.進入死鎖
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.flag = 0;
new Thread(t2).start();
}
代碼中暂吉,
t1創(chuàng)建胖秒,t1先拿到o1的鎖缎患,開始休眠3秒。然后
t2線程創(chuàng)建阎肝,t2拿到o2的鎖挤渔,開始休眠3秒。然后
t1先醒來盗痒,準備拿o2的鎖蚂蕴,發(fā)現(xiàn)o2已經(jīng)加鎖,只能等待o2的鎖釋放俯邓。
t2后醒來骡楼,準備拿o1的鎖,發(fā)現(xiàn)o1已經(jīng)加鎖稽鞭,只能等待o1的鎖釋放鸟整。
t1,t2形成死鎖。
我們查看運行狀態(tài)
三朦蕴、發(fā)現(xiàn)排查死鎖情況
我們利用jdk提供的工具定位死鎖問題:
jps顯示所有當前Java虛擬機進程名及pid.
jstack打印進程堆棧信息篮条。
列出所有java進程。
我們檢查一下DeadLockDemo吩抓,為什么這個線程不退棧涉茧。
jstack 11170
我們直接翻到最后:已經(jīng)檢測出了一個java級別死鎖。其中兩個線程分別卡在了代碼第27行和第41行疹娶。檢查我們代碼的對應(yīng)位置伴栓,即可排查錯誤。此處我們是第二個鎖始終拿不到雨饺,所以死鎖了钳垮。
四、解決辦法
死鎖一旦發(fā)生额港,我們就無法解決了饺窿。所以我們只能避免死鎖的發(fā)生。
既然死鎖需要滿足四種條件移斩,那我們就從條件下手肚医,只要打破任意規(guī)則即可。
(互斥)盡量少用互斥鎖叹哭,能加讀鎖忍宋,不加寫鎖。當然這條無法避免风罩。
(請求和保持)采用資源靜態(tài)分配策略(進程資源靜態(tài)分配方式是指一個進程在建立時就分配了它需要的全部資源).我們盡量不讓線程同時去請求多個鎖,或者在擁有一個鎖又請求不到下個鎖時舵稠,不保持等待超升,先釋放資源等待一段時間在重新請求入宦。
(不剝奪)允許進程剝奪使用其他進程占有的資源。優(yōu)先級室琢。
(循環(huán)等待)盡量調(diào)整獲得鎖的順序乾闰,不發(fā)生嵌套資源請求。加入超時盈滴。