鎖的合理使用能夠保證共享數據的安全性,但是 使用不當也會可能引起死鎖。
1. 死鎖概念
死鎖是指兩個或兩個以上的線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力干涉那它們都將無法推進下去,如果系統資源充足,進程的資源請求都能夠得到滿足,死鎖出現的可能性就很低,否則就會因爭奪有限的資源而陷入死鎖。
2. 死鎖原因
- 互斥條件(Mutual Exclusion)
資源一次只能被一個線程獨占使用。 - 持有并等待(Hold and Wait)
線程在持有至少一個資源的同時,還在等待其他線程持有的資源。 - 不可搶占(No Preemption)
資源不能被強制釋放,只能由持有它的線程主動釋放。 - 循環等待(Circular Wait)
存在一個線程等待鏈,每個線程都在等待下一個線程持有的資源。
當這四個條件同時滿足時,死鎖必然發生。
3. 死鎖演示
下面的一段代碼演示了死鎖:
public static void main(String[] args) {Object lockA = new Object();Object lockB = new Object();new Thread(() -> {synchronized (lockA) {System.out.println(Thread.currentThread().getName() + ", 獲取了🔒A");try {sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lockB) {System.out.println(Thread.currentThread().getName() + ", 獲取了🔒B");}}}, "線程1").start();new Thread(() -> {synchronized (lockB) {System.out.println(Thread.currentThread().getName() + ", 獲取了🔒B");try {sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}synchronized (lockA) {System.out.println(Thread.currentThread().getName() + ", 獲取了🔒A");}}}, "線程2").start();
}
執行結果:
4. 死鎖檢測
4.1 命令
jps -l
jstack pid
jstack 6612
4.2 圖形工具 jconsole
5. 避免死鎖
- 避免一個線程同時獲取多個不同的鎖
- 避免一個線程在鎖內同時占用多個資源 盡量保證每個鎖只占一個資源
- 嘗試使用帶超時時間到鎖 ,例如 lock.tryLock(timeout) 來替代內部鎖機制
- 對于數據庫鎖 加鎖和解鎖必須在同一個數據庫連接里,否則會出現解鎖失敗的情況
- 按固定順序獲取鎖。
- 使用超時機制(
tryLock
)。 - 減少鎖的持有時間和粒度。
- 利用高級并發工具替代顯式鎖。
通過合理設計代碼結構、遵循鎖順序約定,以及利用Java并發工具包,可有效避免死鎖問題。