真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

死鎖的3種死法

1. 什么是死鎖

在多線程環(huán)境中,多個(gè)進(jìn)程可以競(jìng)爭(zhēng)有限數(shù)量的資源。當(dāng)一個(gè)進(jìn)程申請(qǐng)資源時(shí),如果這時(shí)沒(méi)有可用資源,那么這個(gè)進(jìn)程進(jìn)入等待狀態(tài)。有時(shí),如果所申請(qǐng)的資源被其他等待進(jìn)程占有,那么該等待進(jìn)程有可能再也無(wú)法改變狀態(tài)。這種情況稱為死鎖

海曙網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁(yè)設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、成都響應(yīng)式網(wǎng)站建設(shè)公司等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營(yíng)維護(hù)。創(chuàng)新互聯(lián)建站從2013年開始到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來(lái)保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選創(chuàng)新互聯(lián)建站。

在Java中使用多線程,就會(huì)有可能導(dǎo)致死鎖問(wèn)題。死鎖會(huì)讓程序一直住,不再往下執(zhí)行。我們只能通過(guò)中止并重啟的方式來(lái)讓程序重新執(zhí)行。

2. 造成死鎖的原因

  • 當(dāng)前線程擁有其他線程需要的資源
  • 當(dāng)前線程等待其他線程已擁有的資源
  • 都不放棄自己擁有的資源

3. 死鎖的必要條件

3.1 互斥

進(jìn)程要求對(duì)所分配的資源(如打印機(jī))進(jìn)行排他性控制,即在一段時(shí)間內(nèi)某資源僅為一個(gè)進(jìn)程所占有。此時(shí)若有其他進(jìn)程請(qǐng)求該資源,則請(qǐng)求進(jìn)程只能等待。

3.2 不可剝奪

進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走,即只能由獲得該資源的進(jìn)程自己來(lái)釋放(只能是主動(dòng)釋放)。

3.3 請(qǐng)求與保持

進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其他進(jìn)程占有,此時(shí)請(qǐng)求進(jìn)程被阻塞,但對(duì)自己已獲得的資源保持不放。

3.4 循環(huán)等待

是指進(jìn)程發(fā)生死鎖后,必然存在一個(gè)進(jìn)程–資源之間的環(huán)形鏈,通俗講就是你等我的資源,我等你的資源,大家一直等。

4. 死鎖的分類

4.1 靜態(tài)順序型死鎖

線程之間形成相互等待資源的環(huán)時(shí),就會(huì)形成順序死鎖lock-ordering deadlock,多個(gè)線程試圖以不同的順序來(lái)獲取相同的鎖時(shí),容易形成順序死鎖,如果所有線程以固定的順序來(lái)獲取鎖,就不會(huì)出現(xiàn)順序死鎖問(wèn)題

經(jīng)典案例是LeftRightDeadlock,兩個(gè)方法,分別是leftRigth、rightLeft。如果一個(gè)線程調(diào)用leftRight,另一個(gè)線程調(diào)用rightLeft,且兩個(gè)線程是交替執(zhí)行的,就會(huì)發(fā)生死鎖。

public class LeftRightDeadLock {

    //左邊鎖
    private static Object left = new Object();
    //右邊鎖
    private static Object right = new Object();

    /**
     * 現(xiàn)持有左邊的鎖,然后獲取右邊的鎖
     */
    public static void leftRigth() {
        synchronized (left) {
            System.out.println("leftRigth: left lock,threadId:" + Thread.currentThread().getId());
            //休眠增加死鎖產(chǎn)生的概率
            sleep(100);
            synchronized (right) {
                System.out.println("leftRigth: right lock,threadId:" + Thread.currentThread().getId());
            }
        }
    }

    /**
     * 現(xiàn)持有右邊的鎖,然后獲取左邊的鎖
     */
    public static void rightLeft() {
        synchronized (right) {
            System.out.println("rightLeft: right lock,threadId:" + Thread.currentThread().getId());
            //休眠增加死鎖產(chǎn)生的概率
            sleep(100);
            synchronized (left) {
                System.out.println("rightLeft: left lock,threadId:" + Thread.currentThread().getId());
            }
        }
    }

    /**
     * 休眠
     *
     * @param time
     */
    private static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //創(chuàng)建一個(gè)線程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(() -> leftRigth());
        executorService.execute(() -> rightLeft());
        executorService.shutdown();
    }
}

輸出:

leftRigth: left lock,threadId:12
rightLeft: right lock,threadId:13

我們發(fā)現(xiàn),12號(hào)線程鎖住了左邊要向右邊獲取鎖,13號(hào)鎖住了右邊,要向左邊獲取鎖,因?yàn)閮蛇叾疾会尫抛约旱逆i,互不相讓,就產(chǎn)生了死鎖。

4.1.1 解決方案

固定加鎖的順序(針對(duì)鎖順序死鎖)

只要交換下鎖的順序,讓線程來(lái)了之后先獲取同一把鎖,獲取不到就等待,等待上一個(gè)線程釋放鎖再獲取鎖。

public static void leftRigth() {
       synchronized (left) {
         ...
           synchronized (right) {
            ...
           }
       }
   }

   public static void rightLeft() {
       synchronized (left) {
         ...
           synchronized (right) {
            ...
           }
       }
   }

4.2 動(dòng)態(tài)鎖順序型死鎖

由于方法入?yún)⒂赏獠總鬟f而來(lái),方法內(nèi)部雖然對(duì)兩個(gè)參數(shù)按照固定順序進(jìn)行加鎖,但是由于外部傳遞時(shí)順序的不可控,而產(chǎn)生鎖順序造成的死鎖,即動(dòng)態(tài)鎖順序死鎖。

上例告訴我們,交替的獲取鎖會(huì)導(dǎo)致死鎖,且鎖是固定的。有時(shí)候鎖的執(zhí)行順序并不那么清晰,參數(shù)導(dǎo)致不同的執(zhí)行順序。經(jīng)典案例是銀行賬戶轉(zhuǎn)賬,from賬戶向to賬戶轉(zhuǎn)賬,在轉(zhuǎn)賬之前先獲取兩個(gè)賬戶的鎖,然后開始轉(zhuǎn)賬,如果這是to賬戶向from賬戶轉(zhuǎn)賬,角色互換,也會(huì)導(dǎo)致鎖順序死鎖。

/**
 * 動(dòng)態(tài)順序型死鎖
 * 轉(zhuǎn)賬業(yè)務(wù)
 */
public class TransferMoneyDeadlock {

    public static void transfer(Account from, Account to, int amount) {
        //先鎖住轉(zhuǎn)賬的賬戶
        synchronized (from) {
            System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + from.name + "】賬戶鎖成功");
            //休眠增加死鎖產(chǎn)生的概率
            sleep(100);
            //在鎖住目標(biāo)賬戶
            synchronized (to) {
                System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + to.name + "】賬戶鎖成功");
                if (from.balance < amount) {
                    System.out.println("余額不足");
                    return;
                } else {
                    from.debit(amount);
                    to.credit(amount);
                    System.out.println("線程【" + Thread.currentThread().getId() + "】從【" + from.name + "】賬戶轉(zhuǎn)賬到【" + to.name + "】賬戶【" + amount + "】元錢成功");
                }
            }
        }
    }

    private static class Account {
        String name;
        int balance;

        public Account(String name, int balance) {
            this.name = name;
            this.balance = balance;
        }

        void debit(int amount) {
            this.balance = balance - amount;
        }

        void credit(int amount) {
            this.balance = balance + amount;
        }
    }


    /**
     * 休眠
     *
     * @param time
     */
    private static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //創(chuàng)建線程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //創(chuàng)建賬戶A
        Account A = new Account("A", 100);
        //創(chuàng)建賬戶B
        Account B = new Account("B", 200);
        //A -> B 的轉(zhuǎn)賬
        executorService.execute(() -> transfer(A, B, 5));
        //B -> A 的轉(zhuǎn)賬
        executorService.execute(() -> transfer(B, A, 10));
        executorService.shutdown();
    }
}

輸出:

線程【12】獲取【A】賬戶鎖成功
線程【13】獲取【B】賬戶鎖成功

然后就沒(méi)有然后了,產(chǎn)生了死鎖,我們發(fā)現(xiàn) 因?yàn)閷?duì)象的調(diào)用關(guān)系,產(chǎn)生了互相鎖住資源的問(wèn)題。

4.2.1 解決方案

根據(jù)傳入對(duì)象的hashCode硬性確定加鎖順序,消除可變性,避免死鎖。

package com.test.thread.deadlock;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 動(dòng)態(tài)順序型死鎖解決方案
 */
public class TransferMoneyDeadlock {
    /**
     * 監(jiān)視器,第三把鎖,為了方式HASH沖突
     */
    private static Object lock = new Object();

    /**
     * 我們經(jīng)過(guò)上一次得失敗,明白了不能依賴參數(shù)名稱簡(jiǎn)單的確定鎖的順序,因?yàn)閰?shù)是
     * 具有動(dòng)態(tài)性的,所以,我們改變一下思路,直接根據(jù)傳入對(duì)象的hashCode()大小來(lái)
     * 對(duì)鎖定順序進(jìn)行排序(這里要明白的是如何排序不是關(guān)鍵,有序才是關(guān)鍵)。
     *
     * @param from
     * @param to
     * @param amount
     */
    public static void transfer(Account from, Account to, int amount) {
        /**
         * 這里需要說(shuō)明一下為什么不使用HashCode()因?yàn)镠ashCode方法可以被重寫,
         * 所以,我們無(wú)法簡(jiǎn)單的使用父類或者當(dāng)前類提供的簡(jiǎn)單的hashCode()方法,
         * 所以,我們就使用系統(tǒng)提供的identityHashCode()方法,該方法保證無(wú)論
         * 你是否重寫了hashCode方法,都會(huì)在虛擬機(jī)層面上調(diào)用一個(gè)名為JVM_IHashCode
         * 的方法來(lái)根據(jù)對(duì)象的存儲(chǔ)地址來(lái)獲取該對(duì)象的hashCode(),HashCode如果不重寫
         * 的話,其實(shí)也是通過(guò)這個(gè)虛擬機(jī)層面上的方法,JVM_IHashCode()方法實(shí)現(xiàn)的
         * 這個(gè)方法是用C++實(shí)現(xiàn)的。
         */
        int fromHash = System.identityHashCode(from);
        int toHash = System.identityHashCode(to);
        if (fromHash > toHash) {
            //先鎖住轉(zhuǎn)賬的賬戶
            synchronized (from) {
                System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + from.name + "】賬戶鎖成功");
                //休眠增加死鎖產(chǎn)生的概率
                sleep(100);
                //在鎖住目標(biāo)賬戶
                synchronized (to) {
                    System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + to.name + "】賬戶鎖成功");
                    if (from.balance < amount) {
                        System.out.println("余額不足");
                        return;
                    } else {
                        from.debit(amount);
                        to.credit(amount);
                        System.out.println("線程【" + Thread.currentThread().getId() + "】從【" + from.name + "】賬戶轉(zhuǎn)賬到【" + to.name + "】賬戶【" + amount + "】元錢成功");
                    }
                }
            }
        } else if (fromHash < toHash) {
            //先鎖住轉(zhuǎn)賬的賬戶
            synchronized (to) {
                System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + from.name + "】賬戶鎖成功");
                //休眠增加死鎖產(chǎn)生的概率
                sleep(100);
                //在鎖住目標(biāo)賬戶
                synchronized (from) {
                    System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + to.name + "】賬戶鎖成功");
                    if (from.balance < amount) {
                        System.out.println("余額不足");
                        return;
                    } else {
                        from.debit(amount);
                        to.credit(amount);
                        System.out.println("線程【" + Thread.currentThread().getId() + "】從【" + from.name + "】賬戶轉(zhuǎn)賬到【" + to.name + "】賬戶【" + amount + "】元錢成功");
                    }
                }
            }
        } else {
            //如果傳入對(duì)象的Hash值相同,那就加讓加第三層鎖
            synchronized (lock) {
                //先鎖住轉(zhuǎn)賬的賬戶
                synchronized (from) {
                    System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + from.name + "】賬戶鎖成功");
                    //休眠增加死鎖產(chǎn)生的概率
                    sleep(100);
                    //在鎖住目標(biāo)賬戶
                    synchronized (to) {
                        System.out.println("線程【" + Thread.currentThread().getId() + "】獲取【" + to.name + "】賬戶鎖成功");
                        if (from.balance < amount) {
                            System.out.println("余額不足");
                            return;
                        } else {
                            from.debit(amount);
                            to.credit(amount);
                            System.out.println("線程【" + Thread.currentThread().getId() + "】從【" + from.name + "】賬戶轉(zhuǎn)賬到【" + to.name + "】賬戶【" + amount + "】元錢成功");
                        }
                    }
                }
            }
        }

    }

    private static class Account {
        String name;
        int balance;

        public Account(String name, int balance) {
            this.name = name;
            this.balance = balance;
        }

        void debit(int amount) {
            this.balance = balance - amount;
        }

        void credit(int amount) {
            this.balance = balance + amount;
        }
    }


    /**
     * 休眠
     *
     * @param time
     */
    private static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        //創(chuàng)建線程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        //創(chuàng)建賬戶A
        Account A = new Account("A", 100);
        //創(chuàng)建賬戶B
        Account B = new Account("B", 200);
        //A -> B 的轉(zhuǎn)賬
        executorService.execute(() -> transfer(A, B, 5));
        //B -> A 的轉(zhuǎn)賬
        executorService.execute(() -> transfer(B, A, 10));
        executorService.shutdown();
    }
}

輸出

線程【12】獲取【A】賬戶鎖成功
線程【12】獲取【B】賬戶鎖成功
線程【12】從【A】賬戶轉(zhuǎn)賬到【B】賬戶【5】元錢成功
線程【13】獲取【B】賬戶鎖成功
線程【13】獲取【A】賬戶鎖成功
線程【13】從【B】賬戶轉(zhuǎn)賬到【A】賬戶【10】元錢成功

4.3 協(xié)作對(duì)象間的死鎖

在協(xié)作對(duì)象之間可能存在多個(gè)鎖獲取的情況,但是這些獲取多個(gè)鎖的操作并不像在LeftRightDeadLock或transferMoney中那么明顯,這兩個(gè)鎖并不一定必須在同一個(gè)方法中被獲取。如果在持有鎖時(shí)調(diào)用某個(gè)外部方法,那么這就需要警惕死鎖問(wèn)題,因?yàn)樵谶@個(gè)外部方法中可能會(huì)獲取其他鎖,或者阻塞時(shí)間過(guò)長(zhǎng),導(dǎo)致其他線程無(wú)法及時(shí)獲取當(dāng)前被持有的鎖。

上述兩例中,在同一個(gè)方法中獲取兩個(gè)鎖。實(shí)際上,鎖并不一定在同一方法中被獲取。經(jīng)典案例,如出租車調(diào)度系統(tǒng)。

/**
 * 協(xié)作對(duì)象間的死鎖
 */
public class CoordinateDeadlock {
    /**
     * Taxi 類
     */
    static class Taxi {
        private String location;
        private String destination;
        private Dispatcher dispatcher;

        public Taxi(Dispatcher dispatcher, String destination) {
            this.dispatcher = dispatcher;
            this.destination = destination;
        }

        public synchronized String getLocation() {
            return this.location;
        }

        /**
         * 該方法先獲取Taxi的this對(duì)象鎖后,然后調(diào)用Dispatcher類的方法時(shí),又需要獲取
         * Dispatcher類的this方法。
         *
         * @param location
         */
        public synchronized void setLocation(String location) {
            this.location = location;
            System.out.println(Thread.currentThread().getName() + " taxi set location:" + location);
            if (this.location.equals(destination)) {
                dispatcher.notifyAvailable(this);
            }
        }
    }

    /**
     * 調(diào)度類
     */
    static class Dispatcher {
        private Set taxis;
        private Set availableTaxis;

        public Dispatcher() {
            taxis = new HashSet();
            availableTaxis = new HashSet();
        }

        public synchronized void notifyAvailable(Taxi taxi) {
            System.out.println(Thread.currentThread().getName() + " notifyAvailable.");
            availableTaxis.add(taxi);
        }

        /**
         * 打印當(dāng)前位置:有死鎖風(fēng)險(xiǎn)
         * 持有當(dāng)前鎖的時(shí)候,同時(shí)調(diào)用Taxi的getLocation這個(gè)外部方法;而這個(gè)外部方法也是需要加鎖的
         * reportLocation的鎖的順序與Taxi的setLocation鎖的順序完全相反
         */
        public synchronized void reportLocation() {
            System.out.println(Thread.currentThread().getName() + " report location.");
            for (Taxi t : taxis) {
                t.getLocation();
            }
        }

        public void addTaxi(Taxi taxi) {
            taxis.add(taxi);
        }
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        final Dispatcher dispatcher = new Dispatcher();
        final Taxi taxi = new Taxi(dispatcher, "軟件園");
        dispatcher.addTaxi(taxi);
        //先獲取dispatcher鎖,然后是taxi的鎖
        executorService.execute(() -> dispatcher.reportLocation());
        //先獲取taxi鎖,然后是dispatcher的鎖
        executorService.execute(() -> taxi.setLocation("軟件園"));
        executorService.shutdown();
    }
}

4.3.1 解決方案

使用開放調(diào)用,開放調(diào)用指調(diào)用該方法不需要持有鎖。

開放調(diào)用,是指在調(diào)用某個(gè)方法時(shí)不需要持有鎖。開放調(diào)用可以避免死鎖,這種代碼更容易編寫。上述調(diào)度算法完全可以修改為開發(fā)調(diào)用,修改同步代碼塊的范圍,使其僅用于保護(hù)那些涉及共享狀態(tài)的操作,避免在同步代碼塊中執(zhí)行方法調(diào)用。修改Dispatcher的reportLocation方法:

4.3.1.1 setLocation方法
/**
    * 開放調(diào)用,不持有鎖期間進(jìn)行外部方法調(diào)用
    *
    * @param location
    */
   public void setLocation(String location) {
       synchronized (this) {
           this.location = location;
       }
       System.out.println(Thread.currentThread().getName() + " taxi set location:" + location);
       if (this.location.equals(destination)) {
           dispatcher.notifyAvailable(this);
       }
   }
4.3.1.2 reportLocation 方法
/**
       * 同步塊只包含對(duì)共享狀態(tài)的操作代碼
       */
      public synchronized void reportLocation() {
          System.out.println(Thread.currentThread().getName() + " report location.");
          Set taxisCopy;
          synchronized (this) {
              taxisCopy = new HashSet(taxis);
          }
          for (Taxi t : taxisCopy) {
              t.getLocation();
          }
      }

本文由傳智教育博學(xué)谷教研團(tuán)隊(duì)發(fā)布。

如果本文對(duì)您有幫助,歡迎關(guān)注點(diǎn)贊;如果您有任何建議也可留言評(píng)論私信,您的支持是我堅(jiān)持創(chuàng)作的動(dòng)力。

轉(zhuǎn)載請(qǐng)注明出處!


當(dāng)前文章:死鎖的3種死法
本文地址:http://weahome.cn/article/dsoiojj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部