了解死鎖之前,我們要先了解線程的狀態(tài),或者叫線程的生命周期。

線程的狀態(tài)主要分為上圖中的這幾種狀態(tài),這里我們需要注意一下幾點:
- 初始狀態(tài)
new出的一個線程對象,注意此時線程并未執(zhí)行,只有調(diào)用start方法后才會執(zhí)行。 - 運行態(tài)
Java中規(guī)定將兩種合二為一 操作系統(tǒng)分為兩種:- 運行中 -->被Cpu分配了時間片
- 就緒 -->等待被Cpu分配了時間片
- 阻塞態(tài)
有且僅有調(diào)用synchronized關(guān)鍵字且沒有拿到鎖的情況下算阻塞 wait、sleep等不算,算等待或等待超時
lock顯示鎖 沒有拿到鎖 進(jìn)入等待或等待超時狀態(tài),因為Lock底層調(diào)用的是LockSupport
阻塞被動進(jìn)入 等待主動進(jìn)入
定義
死鎖是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖。
造成死鎖的原因及解決辦法
造成死鎖需要以下三個條件,下面我們對這三個條件,進(jìn)行詳細(xì)的分析和解釋。
-
多個操作者 (M>=2) 爭奪多個資源(N>=2) N<=M
為什么需要多個操作者,如果只有一個操作者,還需要加鎖嗎? 很顯然不需要
為什么需要多個資源,如果只有一個資源,誰搶到就是誰的,用完了其他人接著用。
為什么需要N<=M,如果資源數(shù)大于操作者數(shù)量,操作者可以去爭搶沒有被爭搶的資源。
-
爭奪資源順序不對
public class DieSync {
private static Object objectA = new Object();
private static Object objectB = new Object();
private static void MainDo() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (objectA){
System.out.println(threadName + "get objectA");
Thread.sleep(100);
synchronized (objectB){
System.out.println(threadName + "get objectB");
}
}
}
private static void SonDo() throws InterruptedException {
String threadName = Thread.currentThread().getName();
synchronized (objectB){
System.out.println(threadName + "get objectB");
Thread.sleep(100);
synchronized (objectA){
System.out.println(threadName + "get objectA");
}
}
}
private static class SonThread extends Thread{
private String name;
public SonThread(String name) {
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try {
SonDo();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("主線程");
SonThread sonThread = new SonThread("子線程");
sonThread.start();
MainDo();
}
}
主線程先拿到了A,然后去拿B,發(fā)現(xiàn)B被子線程拿了,然后就在這阻塞著
而子線程先拿到了B,然后去拿A,這時候發(fā)現(xiàn)A被主線程拿了,然后也阻塞
兩者互不相讓,你等我,我等你,等到天荒地老,??菔癄€
如果我們將代碼改為,主線程和子線程都先拿A在拿B,這個問題就解決了,這也就是解決辦法,代碼就不演示了,大家可以自行嘗試。
-
拿到資源不放手
這點很好理解,就是說你拿到鎖了,一直不撒手,占著茅坑不出來了,外面的人自然就拉了褲了,哈哈。
我們下面通過段代碼來演示下正確的做法。
public class TryLock {
private static Lock lockA = new ReentrantLock();
private static Lock lockB = new ReentrantLock();
private static void firstToSecond() throws InterruptedException {
String threadNmae = Thread.currentThread().getName();
Random random = new Random();
while (true) {
if (lockA.tryLock()) {
try {
System.out.println(threadNmae + "get lockA");
if (lockB.tryLock()) {
try {
System.out.println(threadNmae + "get lockB");
System.out.println("firstToSecond do work.....");
break;
} finally {
lockB.unlock();
}
}
} finally {
lockA.unlock();
}
}
Thread.sleep(random.nextInt(3));
}
}
private static void secondToFirst() throws InterruptedException {
String threadNmae = Thread.currentThread().getName();
Random random = new Random();
while (true) {
if (lockB.tryLock()) {
try {
System.out.println(threadNmae + "get lockB");
if (lockA.tryLock()) {
try {
System.out.println(threadNmae + "get lockA");
System.out.println("secondToFirst do work.....");
break;
} finally {
lockA.unlock();
}
}
} finally {
lockB.unlock();
}
}
Thread.sleep(random.nextInt(3));
}
}
private static class SecondThread extends Thread{
private String name;
public SecondThread(String name) {
this.name = name;
}
@Override
public void run() {
Thread.currentThread().setName(name);
try {
secondToFirst();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread.currentThread().setName("主線程");
SecondThread secondThread = new SecondThread("子線程");
secondThread.start();
firstToSecond();
}
}
這段代碼的邏輯也很簡單,就是將內(nèi)置鎖換成了顯示鎖,顯示鎖(Lock)可以進(jìn)行嘗試拿鎖,中斷拿鎖等操作。先嘗試拿鎖,拿到了繼續(xù)執(zhí)行,沒拿到就繼續(xù)循環(huán),直到拿到為止。
這里要注意以下兩點:
- 當(dāng)我們使用Lock的時候,一定要記得加try{} finally{}結(jié)構(gòu),在finally中釋放鎖,避免我們代碼拋出異常,導(dǎo)致鎖不釋放,造成死鎖。
- 我想大家都注意到了這行代碼 Thread.sleep(random.nextInt(3));,這行代碼的作用就是避免一直嘗試拿鎖,造成資源浪費,這種現(xiàn)象也叫活鎖。打印結(jié)果太長了,大家有興趣可以自己運行代碼看一下。
總結(jié)
上面我們說造成死鎖有三個條件,條件1的造成原因是因為我們的業(yè)務(wù)需要,沒有辦法解決,所以我們只能從后兩個條件想辦法,具體的解決辦法,上面代碼也給出了示例,主要方法就是
- 調(diào)整拿鎖順序
- 用顯示鎖,嘗試去拿鎖(注意我們上面提到的注意點)