之前參加工作時總是聽說某某功能出現了死鎖,經過簡要的詢問也算是認識到了死鎖.
關於"鎖"的出現這裏就不再贅述了,它的出現就是實現線程同步,維護數據的唯一性,使得多線程訪問共享資源是順序進行的
那麼死鎖是什麼呢?
我的理解是:所有線程都在等待根本不可能被釋放的鎖資源,導致工作無法正常進行
下面就來介紹下死鎖出現的四個條件↓↓↓↓
①互斥條件:
即當資源被一個線程使用(佔有)時,別的線程不能使用
②請求和保持條件:
即當資源請求者在請求其他的資源的同時保持對原有資源的佔有
③不剝奪條件:
資源請求者不能強制從資源佔有者手中奪取資源,資源只能由資源佔有者主動釋放
④環路等待條件:
指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源
最近在刷面試題時遇到"手寫死鎖"的問題,說實話確實無從下手!
一.案例背景:
二.死鎖代碼及其測試結果展示:
public class DeadLock {
//定義鎖A
private static final Object cardA = new Object();
//定義鎖B
private static final Object cardB = new Object();
/**
* 穿過A房間進入B房間
*/
public static void acrossRoomA(){
synchronized (cardA){
System.out.println(Thread.currentThread().getName()+"拿到cardA進入了A房間,等待cardB進入B房間....");
try {
Thread.sleep(500);
acrossRoomB();//需要在A房間內穿越到房間B
System.out.println(Thread.currentThread().getName()+"獲得了cardB,順利進入B房間!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 穿過B房間進入A房間
*/
public static void acrossRoomB(){
synchronized (cardB){
System.out.println(Thread.currentThread().getName()+"拿到cardB進入了B房間,等待cardA進入A房間....");
try {
Thread.sleep(500);
acrossRoomA();//需要在B房間內穿越到房間A
System.out.println(Thread.currentThread().getName()+"獲得了cardA,順利進入A房間!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
acrossRoomA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
acrossRoomB();
}
}).start();
}
}
測試結果:
三.死鎖檢測方式
1.JDK命令行工具jps、jstack
①jps獲取當前運行的java進程的pid
②jstack+pid查看死鎖
2.使用Idea自帶的功能,點擊Dump Threads圖標
四.死鎖的解決辦法
這裏使用信號量Semaphore(可以自行查閱其API)去控制.
信號量可以控制資源能被多少線程訪問,這裏我們指定只能被一個線程訪問,就做到了類似鎖住。而信號量可以指定去獲取的超時時間,我們可以根據這個超時時間,去做一個額外處理。
對於無法成功獲取的情況,一般就是重複嘗試,或指定嘗試的次數,也可以馬上退出!
package deadlock;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class DeadLockSolution {
//定義2個信號量
public static final Semaphore licenceA = new Semaphore(1);
//同一個時刻,只運行多少個進程同時運行指定代碼
public static final Semaphore licenceB = new Semaphore(1);
/**
* 穿過A房間進入B房間
*/
public static void acrossRoomA() {
try {
if (licenceA.tryAcquire(1,TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName()+"獲得A房間許可並進入,等待獲取進入B房間許可....");
Thread.sleep(500);
acrossRoomB();//需要在A房間內穿越到房間B
System.out.println(Thread.currentThread().getName()+"獲得B房間許可,順利進入B房間!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
licenceA.release();//釋放A房間許可
}
}
/**
* 穿過B房間進入A房間
*/
public static void acrossRoomB() {
try {
if (licenceB.tryAcquire(1,TimeUnit.SECONDS)){
System.out.println(Thread.currentThread().getName()+"獲得B房間許可並進入,等待獲取進入A房間許可....");
Thread.sleep(500);
acrossRoomA();//需要在B房間內穿越到房間A
System.out.println(Thread.currentThread().getName()+"獲得A房間許可,順利進入A房間!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
licenceB.release();//釋放B房間許可
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
acrossRoomA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
acrossRoomB();
}
}).start();
}
}
測試結果:
Thread-0獲得A房間許可並進入,等待獲取進入B房間許可....
Thread-1獲得B房間許可並進入,等待獲取進入A房間許可....
Thread-1獲得A房間許可,順利進入A房間!
Thread-0獲得B房間許可,順利進入B房間!