Java死鎖和活鎖的聯(lián)系及說明
本章還是重點(diǎn)介紹下java開發(fā)過程中,會(huì)出現(xiàn)的鎖。
在多線程編程中,死鎖(Deadlock)和活鎖(Livelock)都是線程間協(xié)調(diào)不當(dāng)導(dǎo)致的執(zhí)行困境,但二者的表現(xiàn)和機(jī)制有顯著區(qū)別。
并發(fā)編程中的常見問題
├── 活鎖 (Livelock)
│ ├── 定義:兩個(gè)或多個(gè)進(jìn)程或線程相互之間不斷嘗試執(zhí)行某個(gè)操作,但由于彼此的影響,這些操作都無(wú)法成功完成
│ ├── 特點(diǎn):系統(tǒng)不會(huì)崩潰,但也不會(huì)取得任何進(jìn)展
│ ├── 示例:線程A和線程B互相等待對(duì)方改變標(biāo)志
│
├── 饑餓 (Starvation)
│ ├── 定義:某個(gè)線程或進(jìn)程由于資源競(jìng)爭(zhēng)或調(diào)度策略的原因,長(zhǎng)時(shí)間無(wú)法獲得必要的資源,導(dǎo)致其無(wú)法執(zhí)行
│ ├── 特點(diǎn):某些線程或進(jìn)程始終得不到執(zhí)行的機(jī)會(huì)
│ ├── 示例:低優(yōu)先級(jí)任務(wù)始終無(wú)法執(zhí)行
│
├── 無(wú)鎖 (Lock-Free)
│ ├── 定義:不使用傳統(tǒng)的互斥鎖,而是使用原子操作和內(nèi)存模型來實(shí)現(xiàn)線程安全
│ ├── 特點(diǎn):提高并發(fā)性能,減少鎖的競(jìng)爭(zhēng)
│ ├── 示例:使用 AtomicInteger 實(shí)現(xiàn)線程安全的計(jì)數(shù)器
│
└── 死鎖 (Deadlock)
├── 定義:兩個(gè)或多個(gè)進(jìn)程或線程在執(zhí)行過程中,因爭(zhēng)奪資源而造成的一種相互等待的現(xiàn)象
├── 特點(diǎn):系統(tǒng)陷入停滯狀態(tài),無(wú)法繼續(xù)執(zhí)行
├── 示例:兩個(gè)線程互相等待對(duì)方持有的鎖
1、死鎖

1、產(chǎn)生原因
滿足下面四個(gè)必要條件,則會(huì)產(chǎn)生死鎖現(xiàn)象。
1、互斥條件:
線程對(duì)所分配到的資源進(jìn)行排他性使用,即在一段時(shí)間內(nèi)某資源只由一個(gè)線程占用。如果此時(shí)還有其它線程請(qǐng)求該資源,則請(qǐng)求者只能等待,直至占有資源的線程用畢釋放。
2、請(qǐng)求和保持條件:
線程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其它線程占有,此時(shí)請(qǐng)求線程阻塞,但又對(duì)自己已獲得的其它資源保持不放。
3、不剝奪條件:
線程已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時(shí)由自己釋放。
4、循環(huán)等待條件:
在發(fā)生死鎖時(shí),必然存在一個(gè)線程 —— 資源的環(huán)形鏈,即線程集合 {T0,T1,T2,???,Tn} 中的 T0 正在等待一個(gè) 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是兩個(gè)共享資源。thread1先獲取resource1,然后嘗試獲取resource2。thread2先獲取resource2,然后嘗試獲取resource1。- 由于
thread1和thread2都在等待對(duì)方釋放資源,從而導(dǎo)致死鎖。
由上面可知,死鎖的四個(gè)條件均滿足。
2、死鎖場(chǎng)景

3、解決方案
3.1. 預(yù)防
預(yù)防死鎖:
通過破壞產(chǎn)生死鎖的四個(gè)必要條件中的一個(gè)或幾個(gè)來預(yù)防死鎖的發(fā)生。例如,一次性獲取所有需要的資源,避免請(qǐng)求和保持條件;或者允許資源被剝奪。
避免死鎖:
在資源分配過程中,通過算法來判斷是否會(huì)發(fā)生死鎖,只有在不會(huì)發(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 (帶超時(shí))
方案:使用 ReentrantLock 的 tryLock() 方法,避免無(wú)限期等待,可以增加回退方案,并且超時(shí)情況下線程會(huì)釋放掉鎖。
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 包中的高級(jí)工具類替代顯式鎖。
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)能按某種順序?yàn)樗羞M(jìn)程分配資源,使它們都能順利完成
- 不安全狀態(tài):系統(tǒng)可能進(jìn)入死鎖的狀態(tài)
四種數(shù)據(jù)結(jié)構(gòu):
- Available:可用資源向量
- Max:每個(gè)進(jìn)程最大需求矩陣
- Allocation:已分配資源矩陣
- Need:需求矩陣(Need = Max - Allocation)
實(shí)現(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. 資源請(qǐng)求算法
boolean requestResources(int process, int[] request) {
// 1. 檢查請(qǐng)求是否超過聲明需求
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;
}
}銀行家算法通過以下機(jī)制預(yù)防死鎖:
事前預(yù)防:在資源分配前進(jìn)行安全性檢查
- 只有當(dāng)分配后系統(tǒng)仍處于安全狀態(tài)時(shí)才允許分配
- 避免了系統(tǒng)進(jìn)入可能導(dǎo)致死鎖的狀態(tài)
破壞死鎖必要條件:
- 破壞"循環(huán)等待"條件:通過確保至少有一個(gè)進(jìn)程總能獲得所需資源
- 破壞"占有并等待"條件:通過要求進(jìn)程一次性聲明最大需求
動(dòng)態(tài)決策:
- 每次資源請(qǐng)求都重新評(píng)估系統(tǒng)安全性
- 比靜態(tài)預(yù)防策略更靈活
3.2. 檢測(cè)
檢測(cè)死鎖:
- 通過一定的算法來檢測(cè)系統(tǒng)中是否存在死鎖。
- 例如,資源分配圖算法。
解除死鎖:
- 當(dāng)檢測(cè)到死鎖后,需要采取措施來解除死鎖。
- 例如,剝奪某個(gè)線程的資源,或者終止某個(gè)線程。
2、活鎖
1、介紹
活鎖是指線程不斷改變狀態(tài)來響應(yīng)對(duì)方,但整體任務(wù)無(wú)法推進(jìn)。
線程未阻塞,仍在不斷嘗試執(zhí)行操作,但因相互干擾陷入無(wú)效循環(huán),CPU 利用率不為 0(線程在執(zhí)行無(wú)意義的重試)。
2、場(chǎng)景
- 兩個(gè)線程(
Worker1和Worker2)需要同時(shí)獲取兩把鎖(lock1和lock2)才能工作。 - 如果某個(gè)線程發(fā)現(xiàn)鎖被占用,它會(huì)主動(dòng)釋放自己的鎖并重試(而不是阻塞等待)。
- 由于兩個(gè)線程同時(shí)讓步,導(dǎo)致它們無(wú)限循環(huán)地釋放和重試,但永遠(yuǎn)無(wú)法完成任務(wù)。
線程的調(diào)度由于不停切換,也會(huì)導(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 無(wú)法獲取 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 無(wú)法獲取 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 無(wú)法獲取 lock2,釋放 lock1 并重試
Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試
Worker1 獲取 lock1
Worker2 獲取 lock2
Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試
Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試
...(無(wú)限循環(huán))
現(xiàn)象:
- 兩個(gè)線程都在運(yùn)行(沒有阻塞,不像死鎖)。
- 它們不斷嘗試獲取對(duì)方的鎖,但發(fā)現(xiàn)鎖被占用后主動(dòng)釋放自己的鎖并重試。
- 由于同時(shí)讓步,導(dǎo)致無(wú)限循環(huán),任務(wù)無(wú)法完成。
3、處理方案
1. 引入隨機(jī)退避
示例:
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 {
// 引入隨機(jī)退避
Thread.sleep(random.nextInt(100)); // 隨機(jī)等待0-99ms
if (lock2.tryLock()) {
try {
System.out.println("Worker1 獲取 lock2,完成任務(wù)");
return; // 成功完成任務(wù)
} finally {
lock2.unlock();
}
} else {
System.out.println("Worker1 無(wú)法獲取 lock2,釋放 lock1 并重試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock1.unlock();
}
// 再次隨機(jī)退避
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 {
// 引入隨機(jī)退避
Thread.sleep(random.nextInt(100)); // 隨機(jī)等待0-99ms
if (lock1.tryLock()) {
try {
System.out.println("Worker2 獲取 lock1,完成任務(wù)");
return; // 成功完成任務(wù)
} finally {
lock1.unlock();
}
} else {
System.out.println("Worker2 無(wú)法獲取 lock1,釋放 lock2 并重試");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock2.unlock();
}
// 再次隨機(jī)退避
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 無(wú)法獲取 lock2,釋放 lock1 并重試");
}
} finally {
lock1.unlock();
}
try {
Thread.sleep(100); // 固定等待時(shí)間
} 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 無(wú)法獲取 lock1,釋放 lock2 并重試");
}
} finally {
lock2.unlock();
}
try {
Thread.sleep(100); // 固定等待時(shí)間
} 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)整鎖獲取順序
這個(gè)方案比較推薦。示例:
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 無(wú)法獲取 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 無(wú)法獲取 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ā)程序。在實(shí)際開發(fā)中,應(yīng)當(dāng)優(yōu)先考慮使用java.util.concurrent包提供的高級(jí)并發(fā)工具,而非直接使用低級(jí)的同步機(jī)制。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
springboot整合mongodb并實(shí)現(xiàn)crud步驟詳解
這篇文章主要介紹了springboot整合mongodb并實(shí)現(xiàn)crud,本文分步驟通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-08-08
spring cloud gateway集成hystrix實(shí)戰(zhàn)篇
這篇文章主要介紹了spring cloud gateway集成hystrix實(shí)戰(zhàn),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07
java實(shí)現(xiàn)檢測(cè)是否字符串中包含中文
本文給大家分享了2個(gè)使用java檢測(cè)字符串中是否包含中文的代碼,都非常的實(shí)用,最后附上了各種字符的unicode編碼的范圍,方便我們以后使用正則進(jìn)行匹配檢測(cè)。2015-10-10

