1. 程式人生 > 其它 >用程式碼角度說死鎖的解決方案

用程式碼角度說死鎖的解決方案

1 死鎖問題

1.1 什麼是死鎖

執行緒A,佔有資源A,並且等待佔有資源B。
執行緒B,佔有資源B,並且等待佔有資源A。

1.2 造成死鎖的原因

  1. 互斥
    • 共享的資源,只能夠被一個執行緒佔用。
    • 共享資源只能一對一
  2. 佔有且等待
    • 執行緒A,佔有資源A,等待資源B時,不會釋放資源A。
    • 佔著茅坑不拉屎。
  3. 不可搶佔
    • 其他執行緒,不能夠搶佔別的執行緒的資源。
    • 互相謙讓,看上去和諧,實際上卻解決不了問題。
  4. 迴圈等待
    • 執行緒們一旦進入等待狀態,就會一直等下去。
    • 死腦筋,不懂得變通。

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;
                    }
                }
            }
        }
    }