用程式碼角度說死鎖的解決方案
阿新 • • 發佈:2021-06-11
1 死鎖問題
1.1 什麼是死鎖
執行緒A,佔有資源A,並且等待佔有資源B。
執行緒B,佔有資源B,並且等待佔有資源A。
1.2 造成死鎖的原因
- 互斥
- 共享的資源,只能夠被一個執行緒佔用。
- 共享資源只能一對一
- 佔有且等待
- 執行緒A,佔有資源A,等待資源B時,不會釋放資源A。
- 佔著茅坑不拉屎。
- 不可搶佔
- 其他執行緒,不能夠搶佔別的執行緒的資源。
- 互相謙讓,看上去和諧,實際上卻解決不了問題。
- 迴圈等待
- 執行緒們一旦進入等待狀態,就會一直等下去。
- 死腦筋,不懂得變通。
1.3 從程式碼說是如何避免死鎖的
一旦發生死鎖,沒什麼好辦法,只能重啟。
所以應該從死鎖產生的原因入手,解決死鎖產生的原因,避免死鎖。
1.3.1 互斥
無法改變。這是共享資源定義的客觀事實。
1.3.2 破壞佔有且等待
如果可以一次性申請所有的資源,就可以避免等待。
以兩個賬戶AB轉賬為例子。
public class DeadLock{ public static void main(String[] args){ Account a = new Accout(); Account b = new Accout(); // a向b轉100,b向a轉100 a.transfer(b, 100); b.transfer(a, 100); } } // 一次性申請所有的資源 static class Allocator{ private List<Object> als = new ArrayList<Object>; synchronized boolean apply(Object from, Object to){ if(als.contains(from) || als.contains(to)){ return false; } else{ als.add(from); als.add(to); } return true; } synchronized void clean(Object from, Object to){ als.remove(from); als.remove(to); } } static class Accout{ // 單例設計模式 private Allocator actr = Allocator.getInstance(); private int balance; void transfer(Account target, int amt){ while(!actr.apply(this, target)); try{ // using synchronized 一口氣獲得所有的資源。 synchronized(this){ sout(this.toString() + " lock obj1"); synchronized(target){ sout(this.toString() + " lock obj2"); if(this.balance > amt){ this.balance -= amt; target.balance += amt; } } } } finally{ actr.clean(this, target); } } } private void Allocator(){}; private static class singleTonHoler{ private static Allocator INSTANCE = new Allocator(); } public static Allocator getInstanece(){ return SingleTonHoler.INSTANCE; }
1.3.3 破壞不可搶佔條件
換句話說,當一個執行緒發現自己要死鎖了,就應該把自己佔有的資源釋放出去。
synchronized做不到。當synchronized申請不到資源時,執行緒就直接進入阻塞狀態,自然無法釋放已經佔有資源。
JDK中的java.util.concurrent提供了Lock來解決這個問題。
顯式使用Lock類中的定時tryLock功能來替代內建鎖機制,可以檢測死鎖,並且從死鎖狀態中恢復過來。
使用內建鎖的執行緒獲取不到鎖會被阻塞,而顯式鎖可以指定一個個超時時間(TimeOut),在等待超過這個時間後,tryLock就會返回一個失敗資訊,並且釋放其擁有的資源。
public class DeadLock{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); public static void main(String[] args){ Thread a = new Thread(new Lock1()); Thread b = new Thread(new Lock2()); a.start(); b.start(); } static class Lock1 implements Runnable{ @Override public void run(){ try{ sout("lock1 running"); while(true){ if(lock1.tryLock(1, TimeUnit.MILLISECONDS)){ sout("Lock1 lock obj1"); if(lock2.tryLock(1, TimeUnit.MILLISECONDS)){ sout("Lock1 lock obj2"); } } } } catch (Exception e){ e.printStackTrace; } finally { lock1.unlock(); lock2.unlock(); } } } }
1.3.4 破壞迴圈等待條件
思路是:對系統中的資源進行統一編號,程序可以在任何時刻提出資源申請,必須按照資源的編號順序提出。這樣做就能保證系統不出現死鎖。這就是資源有序分配法。
class Account{
private int id;
private int balance;
void transfer(Account target, int amt){
Account left = this;
Account right = target;
if(this.id > target.id){
left = target;
right = this;
}
synchronized(left){
synchronized(right){
if(this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
}
}
}
}