Java死鎖和活鎖的聯(lián)系及說明
本章還是重點介紹下java開發(fā)過程中,會出現(xiàn)的鎖。
在多線程編程中,死鎖(Deadlock)和活鎖(Livelock)都是線程間協(xié)調(diào)不當(dāng)導(dǎo)致的執(zhí)行困境,但二者的表現(xiàn)和機制有顯著區(qū)別。
并發(fā)編程中的常見問題
├── 活鎖 (Livelock)
│ ├── 定義:兩個或多個進(jìn)程或線程相互之間不斷嘗試執(zhí)行某個操作,但由于彼此的影響,這些操作都無法成功完成
│ ├── 特點:系統(tǒng)不會崩潰,但也不會取得任何進(jìn)展
│ ├── 示例:線程A和線程B互相等待對方改變標(biāo)志
│
├── 饑餓 (Starvation)
│ ├── 定義:某個線程或進(jìn)程由于資源競爭或調(diào)度策略的原因,長時間無法獲得必要的資源,導(dǎo)致其無法執(zhí)行
│ ├── 特點:某些線程或進(jìn)程始終得不到執(zhí)行的機會
│ ├── 示例:低優(yōu)先級任務(wù)始終無法執(zhí)行
│
├── 無鎖 (Lock-Free)
│ ├── 定義:不使用傳統(tǒng)的互斥鎖,而是使用原子操作和內(nèi)存模型來實現(xiàn)線程安全
│ ├── 特點:提高并發(fā)性能,減少鎖的競爭
│ ├── 示例:使用 AtomicInteger 實現(xiàn)線程安全的計數(shù)器
│
└── 死鎖 (Deadlock)
├── 定義:兩個或多個進(jìn)程或線程在執(zhí)行過程中,因爭奪資源而造成的一種相互等待的現(xiàn)象
├── 特點:系統(tǒng)陷入停滯狀態(tài),無法繼續(xù)執(zhí)行
├── 示例:兩個線程互相等待對方持有的鎖
1、死鎖
1、產(chǎn)生原因
滿足下面四個必要條件,則會產(chǎn)生死鎖現(xiàn)象。
1、互斥條件:
線程對所分配到的資源進(jìn)行排他性使用,即在一段時間內(nèi)某資源只由一個線程占用。如果此時還有其它線程請求該資源,則請求者只能等待,直至占有資源的線程用畢釋放。
2、請求和保持條件:
線程已經(jīng)保持了至少一個資源,但又提出了新的資源請求,而該資源已被其它線程占有,此時請求線程阻塞,但又對自己已獲得的其它資源保持不放。
3、不剝奪條件:
線程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。
4、循環(huán)等待條件:
在發(fā)生死鎖時,必然存在一個線程 —— 資源的環(huán)形鏈,即線程集合 {T0,T1,T2,???,Tn} 中的 T0 正在等待一個 T1 占用的資源;T1 正在等待 T2 占用的資源,……,Tn 正在等待已被 T0 占用的資源。
代碼示例:
class DeadlockExample { private static final Object resource1 = new Object(); private static final Object resource2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (resource1) { System.out.println("Thread 1: Holding resource 1..."); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 1: Waiting for resource 2..."); synchronized (resource2) { System.out.println("Thread 1: Holding resource 1 and 2..."); } } }); Thread thread2 = new Thread(() -> { synchronized (resource2) { System.out.println("Thread 2: Holding resource 2..."); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread 2: Waiting for resource 1..."); synchronized (resource1) { System.out.println("Thread 2: Holding resource 1 and 2..."); } } }); thread1.start(); thread2.start(); } }
代碼解釋
resource1
和resource2
是兩個共享資源。thread1
先獲取resource1
,然后嘗試獲取resource2
。thread2
先獲取resource2
,然后嘗試獲取resource1
。- 由于
thread1
和thread2
都在等待對方釋放資源,從而導(dǎo)致死鎖。
由上面可知,死鎖的四個條件均滿足。
2、死鎖場景
3、解決方案
3.1. 預(yù)防
預(yù)防死鎖:
通過破壞產(chǎn)生死鎖的四個必要條件中的一個或幾個來預(yù)防死鎖的發(fā)生。例如,一次性獲取所有需要的資源,避免請求和保持條件;或者允許資源被剝奪。
避免死鎖:
在資源分配過程中,通過算法來判斷是否會發(fā)生死鎖,只有在不會發(fā)生死鎖的情況下才進(jìn)行資源分配。例如,銀行家算法。
1. 順序獲取鎖
方案:確保所有線程以相同的順序獲取鎖,避免不同的鎖獲取順序(最有效方案)。
public class DeadlockSolution1 { private static final Object lock1 = new Object(); private static final Object lock2 = new Object(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { synchronized (lock1) { System.out.println("Thread1 acquired lock1"); // 添加小延遲,增加死鎖概率 try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("Thread1 acquired lock2"); } } }); Thread thread2 = new Thread(() -> { synchronized (lock1) { // 改為先獲取lock1,與thread1順序一致 System.out.println("Thread2 acquired lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lock2) { System.out.println("Thread2 acquired lock2"); } } }); thread1.start(); thread2.start(); } }
2. 使用 tryLock (帶超時)
方案:使用 ReentrantLock
的 tryLock()
方法,避免無限期等待,可以增加回退方案,并且超時情況下線程會釋放掉鎖。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class DeadlockSolution2 { private static final Lock lock1 = new ReentrantLock(); private static final Lock lock2 = new ReentrantLock(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { try { if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread1 acquired lock1"); try { Thread.sleep(100); } catch (InterruptedException e) {} if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread1 acquired lock2"); lock2.unlock(); } else { System.out.println("Thread1 failed to acquire lock2"); } lock1.unlock(); } else { System.out.println("Thread1 failed to acquire lock1"); } } catch (InterruptedException e) { e.printStackTrace(); } }); Thread thread2 = new Thread(() -> { try { if (lock2.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread2 acquired lock2"); try { Thread.sleep(100); } catch (InterruptedException e) {} if (lock1.tryLock(500, TimeUnit.MILLISECONDS)) { System.out.println("Thread2 acquired lock1"); lock1.unlock(); } else { System.out.println("Thread2 failed to acquire lock1"); } lock2.unlock(); } else { System.out.println("Thread2 failed to acquire lock2"); } } catch (InterruptedException e) { e.printStackTrace(); } }); thread1.start(); thread2.start(); } }
3. Phaser
方案:使用 java.util.concurrent
包中的高級工具類替代顯式鎖。
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Phaser; public class DeadlockSolution4 { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(2); Phaser phaser = new Phaser(2); // 使用Phaser協(xié)調(diào)線程 executor.submit(() -> { System.out.println("Task1 started"); phaser.arriveAndAwaitAdvance(); // 等待其他線程 System.out.println("Task1 completed"); }); executor.submit(() -> { System.out.println("Task2 started"); phaser.arriveAndAwaitAdvance(); // 等待其他線程 System.out.println("Task2 completed"); }); executor.shutdown(); } }
4. 銀行家算法
- 安全狀態(tài):系統(tǒng)能按某種順序為所有進(jìn)程分配資源,使它們都能順利完成
- 不安全狀態(tài):系統(tǒng)可能進(jìn)入死鎖的狀態(tài)
四種數(shù)據(jù)結(jié)構(gòu):
- Available:可用資源向量
- Max:每個進(jìn)程最大需求矩陣
- Allocation:已分配資源矩陣
- Need:需求矩陣(Need = Max - Allocation)
實現(xiàn)步驟:
1. 安全狀態(tài)檢查算法
boolean isSafeState() { // 1. 初始化 int[] work = Arrays.copyOf(Available, Available.length); boolean[] finish = new boolean[processCount]; Arrays.fill(finish, false); // 2. 尋找可滿足的進(jìn)程 int count = 0; while (count < processCount) { boolean found = false; for (int i = 0; i < processCount; i++) { if (!finish[i] && checkNeedLessThanWork(i, work)) { // 3. 假設(shè)進(jìn)程i釋放資源 for (int j = 0; j < resourceTypes; j++) { work[j] += Allocation[i][j]; } finish[i] = true; found = true; count++; } } if (!found) break; // 沒有找到可滿足的進(jìn)程 } // 4. 檢查是否所有進(jìn)程都能完成 return count == processCount; } boolean checkNeedLessThanWork(int process, int[] work) { for (int i = 0; i < resourceTypes; i++) { if (Need[process][i] > work[i]) { return false; } } return true; }
2. 資源請求算法
boolean requestResources(int process, int[] request) { // 1. 檢查請求是否超過聲明需求 for (int i = 0; i < resourceTypes; i++) { if (request[i] > Need[process][i]) { return false; } } // 2. 檢查系統(tǒng)是否有足夠資源 for (int i = 0; i < resourceTypes; i++) { if (request[i] > Available[i]) { return false; } } // 3. 嘗試分配 for (int i = 0; i < resourceTypes; i++) { Available[i] -= request[i]; Allocation[process][i] += request[i]; Need[process][i] -= request[i]; } // 4. 檢查安全性 if (isSafeState()) { return true; } else { // 回滾分配 for (int i = 0; i < resourceTypes; i++) { Available[i] += request[i]; Allocation[process][i] -= request[i]; Need[process][i] += request[i]; } return false; } }
銀行家算法通過以下機制預(yù)防死鎖:
事前預(yù)防:在資源分配前進(jìn)行安全性檢查
- 只有當(dāng)分配后系統(tǒng)仍處于安全狀態(tài)時才允許分配
- 避免了系統(tǒng)進(jìn)入可能導(dǎo)致死鎖的狀態(tài)
破壞死鎖必要條件:
- 破壞"循環(huán)等待"條件:通過確保至少有一個進(jìn)程總能獲得所需資源
- 破壞"占有并等待"條件:通過要求進(jìn)程一次性聲明最大需求
動態(tài)決策:
- 每次資源請求都重新評估系統(tǒng)安全性
- 比靜態(tài)預(yù)防策略更靈活
3.2. 檢測
檢測死鎖:
- 通過一定的算法來檢測系統(tǒng)中是否存在死鎖。
- 例如,資源分配圖算法。
解除死鎖:
- 當(dāng)檢測到死鎖后,需要采取措施來解除死鎖。
- 例如,剝奪某個線程的資源,或者終止某個線程。
2、活鎖
1、介紹
活鎖是指線程不斷改變狀態(tài)來響應(yīng)對方,但整體任務(wù)無法推進(jìn)。
線程未阻塞,仍在不斷嘗試執(zhí)行操作,但因相互干擾陷入無效循環(huán),CPU 利用率不為 0(線程在執(zhí)行無意義的重試)。
2、場景
- 兩個線程(
Worker1
和Worker2
)需要同時獲取兩把鎖(lock1
和lock2
)才能工作。 - 如果某個線程發(fā)現(xiàn)鎖被占用,它會主動釋放自己的鎖并重試(而不是阻塞等待)。
- 由于兩個線程同時讓步,導(dǎo)致它們無限循環(huán)地釋放和重試,但永遠(yuǎn)無法完成任務(wù)。
線程的調(diào)度由于不停切換,也會導(dǎo)致。
代碼示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LivelockExample { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); public void worker1() { while (true) { lock1.lock(); System.out.println("Worker1 獲取 lock1"); // 嘗試獲取 lock2,如果失敗就釋放 lock1 并重試 if (lock2.tryLock()) { System.out.println("Worker1 獲取 lock2,完成任務(wù)"); lock2.unlock(); lock1.unlock(); break; } else { System.out.println("Worker1 無法獲取 lock2,釋放 lock1 并重試"); lock1.unlock(); } // 短暫休眠,避免 CPU 100% try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void worker2() { while (true) { lock2.lock(); System.out.println("Worker2 獲取 lock2"); // 嘗試獲取 lock1,如果失敗就釋放 lock2 并重試 if (lock1.tryLock()) { System.out.println("Worker2 獲取 lock1,完成任務(wù)"); lock1.unlock(); lock2.unlock(); break; } else { System.out.println("Worker2 無法獲取 lock1,釋放 lock2 并重試"); lock2.unlock(); } // 短暫休眠,避免 CPU 100% try { Thread.sleep(10); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { LivelockExample example = new LivelockExample(); Thread t1 = new Thread(example::worker1); Thread t2 = new Thread(example::worker2); t1.start(); t2.start(); } }
以下是可能的執(zhí)行順序:
Worker1 獲取 lock1
Worker2 獲取 lock2
Worker1 無法獲取 lock2,釋放 lock1 并重試
Worker2 無法獲取 lock1,釋放 lock2 并重試
Worker1 獲取 lock1
Worker2 獲取 lock2
Worker1 無法獲取 lock2,釋放 lock1 并重試
Worker2 無法獲取 lock1,釋放 lock2 并重試
...(無限循環(huán))
現(xiàn)象:
- 兩個線程都在運行(沒有阻塞,不像死鎖)。
- 它們不斷嘗試獲取對方的鎖,但發(fā)現(xiàn)鎖被占用后主動釋放自己的鎖并重試。
- 由于同時讓步,導(dǎo)致無限循環(huán),任務(wù)無法完成。
3、處理方案
1. 引入隨機退避
示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.Random; public class RandomBackoffSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); private final Random random = new Random(); public void worker1() { while (true) { lock1.lock(); System.out.println("Worker1 獲取 lock1"); try { // 引入隨機退避 Thread.sleep(random.nextInt(100)); // 隨機等待0-99ms if (lock2.tryLock()) { try { System.out.println("Worker1 獲取 lock2,完成任務(wù)"); return; // 成功完成任務(wù) } finally { lock2.unlock(); } } else { System.out.println("Worker1 無法獲取 lock2,釋放 lock1 并重試"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock1.unlock(); } // 再次隨機退避 try { Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void worker2() { while (true) { lock2.lock(); System.out.println("Worker2 獲取 lock2"); try { // 引入隨機退避 Thread.sleep(random.nextInt(100)); // 隨機等待0-99ms if (lock1.tryLock()) { try { System.out.println("Worker2 獲取 lock1,完成任務(wù)"); return; // 成功完成任務(wù) } finally { lock1.unlock(); } } else { System.out.println("Worker2 無法獲取 lock1,釋放 lock2 并重試"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock2.unlock(); } // 再次隨機退避 try { Thread.sleep(random.nextInt(100)); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { RandomBackoffSolution solution = new RandomBackoffSolution(); new Thread(solution::worker1).start(); new Thread(solution::worker2).start(); } }
2. 限制重試次數(shù)
示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class RetryLimitSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); private static final int MAX_RETRIES = 5; public void worker1() { int retryCount = 0; while (retryCount < MAX_RETRIES) { retryCount++; lock1.lock(); System.out.println("Worker1 獲取 lock1 (嘗試 " + retryCount + "/" + MAX_RETRIES + ")"); try { if (lock2.tryLock()) { try { System.out.println("Worker1 獲取 lock2,完成任務(wù)"); return; } finally { lock2.unlock(); } } else { System.out.println("Worker1 無法獲取 lock2,釋放 lock1 并重試"); } } finally { lock1.unlock(); } try { Thread.sleep(100); // 固定等待時間 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("Worker1 達(dá)到最大重試次數(shù),放棄任務(wù)"); } public void worker2() { int retryCount = 0; while (retryCount < MAX_RETRIES) { retryCount++; lock2.lock(); System.out.println("Worker2 獲取 lock2 (嘗試 " + retryCount + "/" + MAX_RETRIES + ")"); try { if (lock1.tryLock()) { try { System.out.println("Worker2 獲取 lock1,完成任務(wù)"); return; } finally { lock1.unlock(); } } else { System.out.println("Worker2 無法獲取 lock1,釋放 lock2 并重試"); } } finally { lock2.unlock(); } try { Thread.sleep(100); // 固定等待時間 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("Worker2 達(dá)到最大重試次數(shù),放棄任務(wù)"); } public static void main(String[] args) { RetryLimitSolution solution = new RetryLimitSolution(); new Thread(solution::worker1).start(); new Thread(solution::worker2).start(); } }
3. 調(diào)整鎖獲取順序
這個方案比較推薦。示例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class OrderedLockSolution { private final Lock lock1 = new ReentrantLock(); private final Lock lock2 = new ReentrantLock(); // 定義全局鎖獲取順序 private static final Object lockOrder = new Object(); public void worker1() { while (true) { // 按照固定順序獲取鎖 synchronized (lockOrder) { lock1.lock(); System.out.println("Worker1 獲取 lock1"); try { if (lock2.tryLock()) { try { System.out.println("Worker1 獲取 lock2,完成任務(wù)"); return; } finally { lock2.unlock(); } } else { System.out.println("Worker1 無法獲取 lock2,釋放 lock1 并重試"); } } finally { lock1.unlock(); } } try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public void worker2() { while (true) { // 按照相同順序獲取鎖 synchronized (lockOrder) { lock1.lock(); System.out.println("Worker2 獲取 lock1"); try { if (lock2.tryLock()) { try { System.out.println("Worker2 獲取 lock2,完成任務(wù)"); return; } finally { lock2.unlock(); } } else { System.out.println("Worker2 無法獲取 lock2,釋放 lock1 并重試"); } } finally { lock1.unlock(); } } try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } public static void main(String[] args) { OrderedLockSolution solution = new OrderedLockSolution(); new Thread(solution::worker1).start(); new Thread(solution::worker2).start(); } }
3、聯(lián)系
總結(jié)
理解死鎖和活鎖的區(qū)別與聯(lián)系,有助于開發(fā)出更健壯的并發(fā)程序。在實際開發(fā)中,應(yīng)當(dāng)優(yōu)先考慮使用java.util.concurrent
包提供的高級并發(fā)工具,而非直接使用低級的同步機制。
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot整合mongodb并實現(xiàn)crud步驟詳解
這篇文章主要介紹了springboot整合mongodb并實現(xiàn)crud,本文分步驟通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-08-08spring cloud gateway集成hystrix實戰(zhàn)篇
這篇文章主要介紹了spring cloud gateway集成hystrix實戰(zhàn),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07