<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          誰還沒經(jīng)歷過死鎖呢?

          共 7094字,需瀏覽 15分鐘

           ·

          2022-05-14 02:15

          之前剛學(xué)習(xí)多線程時(shí),由于各種鎖的操作不當(dāng),經(jīng)常不經(jīng)意間程序?qū)懥舜a就發(fā)生了死鎖,不是在灰度測(cè)試的時(shí)候被測(cè)出來,就是在代碼review的時(shí)候被提前發(fā)現(xiàn)。

          這種死鎖的經(jīng)歷不知道大家有沒有,不過怎么說都是一個(gè)面試高頻題目,面試官是肯定希望你經(jīng)歷過的,沒經(jīng)歷過那也得看看某八股文職業(yè)選手的文章裝作經(jīng)歷過

          那么什么是死鎖呢?為什么會(huì)產(chǎn)生死鎖呢?

          什么是死鎖

          敖丙和小美是公司同事,今天他們參加了兩個(gè)不同主題的會(huì)議。但是只有一臺(tái)筆記本電腦,一個(gè)投影儀。敖丙拿了筆記本,小美拿了投影儀。

          那么會(huì)議開了一半,我發(fā)現(xiàn):不行啊!開會(huì)除了筆記本電腦還需要投影給別的同事看啊,而小美在另一個(gè)會(huì)議室也發(fā)現(xiàn)了,自己只拿個(gè)投影儀沒啥用啊,這里連電腦都沒有。

          于是,我需要小美的投影儀,小美需要敖丙的電腦,他們都需要對(duì)方手里的資源,但是又不能放棄自己所持有的。

          所以兩個(gè)會(huì)議都開不下去了。

          就是因?yàn)檫@個(gè)原因,讓會(huì)議進(jìn)程耽擱了兩個(gè)小時(shí)。兩邊的老板都炸了:“ 開會(huì)前怎么連這些都沒準(zhǔn)備好,還想不想干了?!”

          于是老板讓敖丙寫個(gè)檢討好好復(fù)盤整個(gè)事情,以及產(chǎn)生問題的原因。

          細(xì)心的傻瓜一定發(fā)現(xiàn)了,為什么小美不用寫呢?

          當(dāng)然因?yàn)樾∶栏习迨?.....親戚呀~

          上面的問題其實(shí)就是死鎖,我就想著能不能用代碼描述整個(gè)過程。

          于是在檢討上寫了以下這段代碼:

          public?class?DeadLockDemo?{
          ????public?static?Object?lock1?=?new?Object();?//獲取筆記本電腦
          ????public?static?Object?lock2?=?new?Object();?//獲取投影儀

          ????public?static?void?main(String[]?args)?{
          ????????new?Aobing().start();
          ????????new?Xiaomei().start();
          ????}

          ????private?static?class?Aobing?extends?Thread?{
          ????????@Override
          ????????public?void?run()?{
          ????????????synchronized?(lock1)?{
          ????????????????System.out.println("Aobing獲取到筆記本電腦");
          ????????????????try?{
          ????????????????????Thread.sleep(1000);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????System.out.println("Aobing被中斷了!");
          ????????????????}
          ????????????????System.out.println("Aobing正在等待投影儀");
          ????????????????synchronized?(lock2)?{
          ????????????????????System.out.println("Aobing獲取到投影儀");
          ????????????????????try?{
          ????????????????????????Thread.sleep(1000);
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????System.out.println("Aobing被中斷了");
          ????????????????????}
          ????????????????}
          ????????????????System.out.println("Aobing釋放投影儀");
          ????????????}
          ????????????System.out.println("Aobing釋放筆記本電腦");
          ????????}
          ????}

          ????private?static?class?Xiaomei?extends?Thread?{
          ????????@Override
          ????????public?void?run()?{
          ????????????synchronized?(lock2)?{
          ????????????????System.out.println("Xiaomei獲取到投影儀");
          ????????????????try?{
          ????????????????????Thread.sleep(1000);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????System.out.println("Xiaomei被中斷了!");
          ????????????????}
          ????????????????System.out.println("Xiaomei正在等待筆記本電腦");
          ????????????????synchronized?(lock1)?{
          ????????????????????System.out.println("Xiaomei獲取到筆記本電腦");
          ????????????????????try?{
          ????????????????????????Thread.sleep(1000);
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????System.out.println("Xiaomei被中斷了!");
          ????????????????????}
          ????????????????}
          ????????????????System.out.println("Xiaomei釋放筆記本電腦");
          ????????????}
          ????????????System.out.println("Xiaomei釋放投影儀");
          ????????}
          ????}
          }

          從上面程序看出來了,Aobing和Xiaomei兩個(gè)線程都需要獲取鎖去訪問各自的臨界區(qū) ,但是它們又分別依賴對(duì)方的資源。

          于是兩個(gè)線程就同時(shí)進(jìn)入了等待對(duì)方資源釋放的情況,但是誰都無法釋放。

          這就造成了死鎖的狀況。

          死鎖排查

          但是這僅僅只是一個(gè)大概率的猜測(cè),已經(jīng)知道程序出現(xiàn)了異常,又如何第一時(shí)間排查是不是死鎖呢?我繼續(xù)研究了起來。

          他通過Java提供的一些檢測(cè)方式,進(jìn)行了快速的定位。

          Jps & Jstack

          Jps是Jdk自帶的一個(gè)工具,可以查看到正在運(yùn)行的Java進(jìn)程:

          ok,可以看到。DeadLockDemo的進(jìn)程ID是1884,拿到這個(gè)進(jìn)程ID,再使用jstack命令。

          jstack是Java性能排查的利器,主要用來實(shí)時(shí)跟蹤進(jìn)程里對(duì)應(yīng)線程的堆棧信息,可以將Jvm進(jìn)程內(nèi)的所有線程的調(diào)用棧都打印出來。

          所以,直接跟蹤1884這個(gè)進(jìn)程ID就行。

          果然,可以看到,jstack已經(jīng)檢測(cè)到了死鎖。并且Aobing和xiaomei兩個(gè)線程都在互相等待對(duì)方的鎖釋放,也就是阻塞狀態(tài)

          從這里,我確認(rèn)程序發(fā)生了死鎖**。

          馬上跑過去對(duì)正在和小美喝咖啡的老說說:“ 老板,你看這真的不是我的錯(cuò)啊,是咱公司資源不夠,發(fā)生了死鎖!我寫個(gè)程序都跑不出結(jié)果!”

          老板道貌儼然地點(diǎn)了點(diǎn)頭。“嗯,那你還是得想想怎么解決,一個(gè)問題不能連續(xù)犯兩次!”

          于是在當(dāng)天深夜11點(diǎn),敖丙進(jìn)行了深刻的自我反思,默默的寫下這篇文章:「一個(gè)關(guān)于死鎖的故事」。

          死鎖的類型

          OK,看完了上面的故事,我們回過頭來,繼續(xù)來講關(guān)于死鎖的知識(shí)。

          關(guān)于死鎖有幾種類型呢?主要有三種:

          • 一般性死鎖:這是最經(jīng)典的死鎖方式。指的是多線程環(huán)境下每個(gè)線程都需要多個(gè)資源去執(zhí)行,但是這些資源又分別被不同的線程占有著,這就造成了一種僵持的狀態(tài)。
          • 嵌套性死鎖:指的就是鎖的互相嵌套使用。我們上面故事的死鎖類型,其實(shí)就屬于嵌套性死鎖。
          • 重入性死鎖:指的是多線程環(huán)境下,若當(dāng)前線程重復(fù)調(diào)用一個(gè)方法則可能因?yàn)榇a邏輯里的邊界情況從而導(dǎo)致死鎖。

          所以后來Java中無論是Synchronized還是Lock在可重入方面都會(huì)維護(hù)一個(gè)計(jì)數(shù)器來記錄當(dāng)前線程的重入次數(shù),從而進(jìn)入不同的代碼邏輯,就是為了避免死鎖的發(fā)生。

          死鎖原理

          那么有的小伙伴就會(huì)擔(dān)心了:“聽你這么分析,我以后都不敢隨意用它們了,這要是背鍋了可怎么辦!”。

          別擔(dān)心,死鎖哪有那么容易發(fā)生呢。

          你應(yīng)該問一個(gè)問題:程序?yàn)槭裁磿?huì)出現(xiàn)死鎖,或者說在什么情況下,程序才會(huì)出現(xiàn)死鎖。

          要產(chǎn)生死鎖,必須保證你的資源要能夠滿足以下條件,并且缺一不可

          • 互斥條件

          某資源一次只能一個(gè)線程訪問,該資源只要分配給某個(gè)線程,其它線程就無法再訪問,直到該線程訪問結(jié)束。

          • 請(qǐng)求與保持條件

          線程在已經(jīng)占有至少一個(gè)資源的情況下還可以繼續(xù)請(qǐng)求占有資源。

          • 不可搶占條件

          資源若已被其它線程占有,那么想要獲取它就只能等待,不能因?yàn)槟阈枰撡Y源就將其搶占。

          • 循環(huán)等待條件

          在競(jìng)爭(zhēng)環(huán)境中存在一個(gè)線程等待鏈,使得每個(gè)線程都占有上一個(gè)線程所需的至少一種資源。

          也就是說只有以上四個(gè)條件同時(shí)滿足,線程才會(huì)因?yàn)橘Y源分配產(chǎn)生矛盾,死鎖才有可能發(fā)生。

          大家可以類比一下,敖丙和小美是不是就處于以上四個(gè)條件中呢。

          所以說,不要擔(dān)心,想要發(fā)生死鎖還是非常不容易滴。

          死鎖解除

          那當(dāng)你確定了程序發(fā)生了死鎖,怎么辦呢?

          當(dāng)然是不要慌,先給文章點(diǎn)個(gè)贊,收藏一下先,確保以后能找到。

          我們剛剛說了,死鎖發(fā)生的情況是要同時(shí)滿足互斥、請(qǐng)求與保持、不可剝奪、循環(huán)等待這四個(gè)條件,缺一不可。那么我們?nèi)绻胍獬梨i,是不是只要將這四個(gè)條件的任意一個(gè)破壞掉就好了呢?

          • 破壞請(qǐng)求與保持條件

          請(qǐng)求與保持指線程請(qǐng)求資源的同時(shí)必須始終持有資源,所以我們可以在線程開始運(yùn)行之前,一次性地申請(qǐng)其在整個(gè)運(yùn)行過程中所需的全部資源。直至使用完再釋放。

          • 破壞不可搶占條件

          想要達(dá)到這個(gè)目的代表著你要去搶占別的線程已經(jīng)或正在持有的資源,這對(duì)于Synchronized是無能為力的。但是我們可以使用Lock呀!在JDK層面,juc包(java.util.concurrent)提供的Lock可以輕輕松松做到。

          • 破壞循環(huán)等待條件

          若是每個(gè)線程都依賴上一線程所持有的資源,那么整個(gè)線程鏈就會(huì)像閉環(huán)的貪吃蛇一樣,導(dǎo)致資源無法被釋放。因此就需要某一個(gè)線程釋放資源,從而打破循環(huán)。

          所以,我們平時(shí)的代碼要如何設(shè)計(jì)才能盡量避免死鎖的發(fā)生呢?

          盡量將程序設(shè)置為可中斷的

          將程序設(shè)置為可中斷的,這樣在死鎖環(huán)境下如果某個(gè)線程收到中斷請(qǐng)求之后就可以主動(dòng)地釋放掉手中的資源。

          Java多線程中有一個(gè)重要的方法interrupt(),這個(gè)方法可以請(qǐng)求調(diào)用此方法的線程觸發(fā)中斷機(jī)制,該線程可以自身決定是否釋放資源。若是已經(jīng)發(fā)生了死鎖,只要它放棄資源便可打破。

          為鎖添加時(shí)限

          除此之外還可以為嘗試獲取鎖的線程添加一個(gè)超時(shí)等待時(shí)間。若線程在規(guī)定時(shí)間內(nèi)獲取不到鎖則放棄,這樣就可以避免線程無腦請(qǐng)求,同時(shí)也會(huì)釋放該線程已有的資源,讓其它線程有機(jī)會(huì)獲取到鎖,可以開放化一個(gè)相對(duì)封閉的資源環(huán)境。

          保持加鎖順序

          對(duì)于多個(gè)線程如果需要對(duì)方所持有的鎖,那么就要盡量按照相同的順序加鎖,這樣就能夠避免因?yàn)楦鱾€(gè)線程獲取鎖的順序混亂導(dǎo)致死鎖。

          我們?cè)倩剡^頭來看看那個(gè)關(guān)于死鎖的故事。

          經(jīng)過昨天加班的深刻反思,我重新編寫了這段代碼:

          public?class?DeadLockDemo?{
          ????public?static?Object?lock1?=?new?Object();??//獲取筆記本電腦
          ????public?static?Object?lock2?=?new?Object();??//獲取投影儀

          ????public?static?void?main(String[]?args)?{
          ????????new?Thread1().start();
          ????????new?Thread2().start();
          ????}

          ????private?static?class?Thread1?extends?Thread?{
          ????????@Override
          ????????public?void?run()?{
          ????????????synchronized?(lock1)?{
          ????????????????System.out.println("Aobing獲取到筆記本電腦");
          ????????????????try?{
          ????????????????????Thread.sleep(1000);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????System.out.println("Aobing被中斷了!");
          ????????????????}
          ????????????????System.out.println("Aobing正在等待投影儀");
          ????????????????synchronized?(lock2)?{
          ????????????????????System.out.println("Aobing獲取到投影儀");
          ????????????????????try?{
          ????????????????????????Thread.sleep(1000);
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????System.out.println("Aobing被中斷了");
          ????????????????????}
          ????????????????}
          ????????????????System.out.println("Aobing釋放投影儀");
          ????????????}
          ????????????System.out.println("Aobing釋放筆記本電腦");
          ????????}
          ????}

          ????private?static?class?Thread2?extends?Thread?{
          ????????@Override
          ????????public?void?run()?{
          ????????????synchronized?(lock1)?{
          ????????????????System.out.println("Xiaomei獲取到筆記本電腦");
          ????????????????try?{
          ????????????????????Thread.sleep(1000);
          ????????????????}?catch?(InterruptedException?e)?{
          ????????????????????System.out.println("Xiaomei被中斷了!");
          ????????????????}
          ????????????????System.out.println("Xiaomei正在等待投影儀");
          ????????????????synchronized?(lock2){
          ????????????????????System.out.println("Xiaomei獲取到了投影儀");
          ????????????????????try?{
          ????????????????????????Thread.sleep(1000);
          ????????????????????}?catch?(InterruptedException?e)?{
          ????????????????????????System.out.println("Xiaomei被中斷了!");
          ????????????????????}
          ????????????????}
          ????????????????System.out.println("Xiaomei釋放投影儀");
          ????????????}
          ????????????System.out.println("Xiaomei釋放筆記本電腦");
          ????????}
          ????}
          }

          這段代碼和一開始的有什么區(qū)別呢?這次它們獲取鎖的順序是相同的。

          Aobing和Xiaomei兩個(gè)線程都是先獲取lock1再獲取lock2,這樣子兩個(gè)線程誰先獲取到資源,誰就一次性持有資源,直到資源都是釋放完畢再讓下一個(gè)線程獲取,避免相互爭(zhēng)奪導(dǎo)致資源混亂,破壞了請(qǐng)求與保持條件。

          程序也成功運(yùn)行結(jié)束:

          所以我決定在下次開會(huì)的時(shí)候和小美的會(huì)議時(shí)間分開。由我先一次性獲取所有資源開啟他的會(huì)議,結(jié)束后資源再還給小美。

          我抱著電腦高興地將這個(gè)方案告訴了老板。

          第二天,由于和小美的友好配合,兩個(gè)會(huì)議都愉快的開完了,會(huì)議過程非常流暢。老板很開心,決定讓我擔(dān)任會(huì)議編排委員,并且以后會(huì)議室不再購入新設(shè)備!

          我也高興壞了,這下不僅升職加薪不再是夢(mèng),老板和小美的關(guān)系也更融洽了呢。

          總結(jié)

          以上就是關(guān)于我和小美還有老板的故事,其實(shí)生活中死鎖的場(chǎng)景有很多,就像雞生蛋蛋生雞一樣,就是一個(gè)典型的死鎖Bug。都說藝術(shù)來源于生活,看來Bug也來源于生活,等量代換一下,Bug 不 就 等 于 藝 術(shù)?

          我這該死的才華啊,大家覺得有點(diǎn)東西的,可以評(píng)論區(qū)打下“有點(diǎn)東西”。

          我是敖丙,感謝各位的三連,你知道的越多,你不知道的越多,我們下期見。

          瀏覽 61
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  男人天堂中文字幕 | 破处毛片| 熟女草逼| 国产精品一区二区三区四区 | 亚洲性无码视频 |