shutdownHook死鎖問(wèn)題解決

最近碰到一個(gè)問(wèn)題假夺,通過(guò)腳本執(zhí)行kill -15后淮蜈,程序并沒(méi)有退出,進(jìn)程一直都在已卷,最后被退出腳本的通過(guò)kill -9梧田,殺死。導(dǎo)致數(shù)據(jù)完整性被破壞悼尾,程序再重啟后不可用柿扣。通過(guò)排查認(rèn)后發(fā)現(xiàn)是在執(zhí)行shutdownHook時(shí)死鎖程序死鎖。

復(fù)現(xiàn)問(wèn)題

導(dǎo)致問(wèn)題的代碼闺魏,

通過(guò)定位發(fā)現(xiàn)未状,程序在

public class Test {
  private static final Object lock = new Object();

  public static void main(String... args) {
    Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("Locking");
        synchronized (lock) {
          System.out.println("Locked");
        }
      }
    }));
    synchronized (lock) {
      System.out.println("Exiting");
      System.exit(0);
    }
  }
}

輸出:

Exiting
Locking

原因

排查原因
分析一下 addShutdownHook 這個(gè)方法是怎么執(zhí)行的,重點(diǎn)是 ApplicationShutdownHooks析桥,每一個(gè) shutdownHook 都使用一個(gè)Thread包裝司草。

    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }

重點(diǎn):hooks艰垂,每個(gè) hook線程put到hooks中。

    static synchronized void add(Thread hook) {
        if(hooks == null)
            throw new IllegalStateException("Shutdown in progress");

        if (hook.isAlive())
            throw new IllegalArgumentException("Hook already running");

        if (hooks.containsKey(hook))
            throw new IllegalArgumentException("Hook previously registered");

        hooks.put(hook, hook);
    }

添加后誰(shuí)來(lái)處理shutdown這個(gè)操作埋虹,是 Shutdown.add 這里起了一個(gè)線程猜憎,處理所以主要的邏輯在 runHooks

    static {
        try {
            Shutdown.add(1 /* shutdown hook invocation order */,
                false /* not registered if shutdown in progress */,
                new Runnable() {
                    public void run() {
                        runHooks();
                    }
                }
            );
            hooks = new IdentityHashMap<>();
        } catch (IllegalStateException e) {
            // application shutdown hooks cannot be added if
            // shutdown is in progress.
            hooks = null;
        }
    }

這段代碼中 hook.start(); 調(diào)用執(zhí)行 hook的方法,之后調(diào)用 hook.join釋放執(zhí)行權(quán)搔课。
問(wèn)題就出在 hook.join上胰柑,程序執(zhí)行到這里之后,卡住死鎖爬泥,出不去了柬讨。
為什么,因?yàn)?join 實(shí)際就是 wait(0)袍啡,一旦當(dāng)前線程調(diào)用wait(0)踩官,就相當(dāng)于釋放執(zhí)行權(quán),等待其實(shí)線程notify()才能繼續(xù)執(zhí)行境输。
但是main線程調(diào)用System.exit(0)后蔗牡,synchronized 當(dāng)前線程為 main,hook.join拿不到被main未釋放的鎖嗅剖,所以卡住

    static void runHooks() {
        Collection<Thread> threads;
        synchronized(ApplicationShutdownHooks.class) {
            threads = hooks.keySet();
            hooks = null;
        }

        for (Thread hook : threads) {
            hook.start();
        }
        for (Thread hook : threads) {
            while (true) {
                try {
                    hook.join();
                    break;
                } catch (InterruptedException ignored) {
                }
            }
        }
    }

通過(guò)工具排查

死鎖.png

再看線程狀態(tài)

通過(guò)代碼線程堆棧來(lái)確認(rèn)就是這個(gè)原因

  1. main 方法是:WAIT 狀態(tài)
  2. Thread-0是:RUNNING 狀態(tài)辩越,但是進(jìn)入synchronized之后就會(huì)BLOCKED住

這里就對(duì)應(yīng)上圖的兩個(gè)線程的狀態(tài)

解決

移除 shutdownHook 中不必要的加鎖。

  1. 移除 shutdownHook 中不必要的加鎖信粮,shutdown 場(chǎng)景中很不需要用到加鎖
  2. 使用不同的加鎖對(duì)象区匣,如果一定需要加鎖,可以在 shutdownHook 的線程內(nèi)使用一把新的鎖蒋院,這樣即可以保證安全性亏钩,又不會(huì)死鎖。

博客同步更新

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末欺旧,一起剝皮案震驚了整個(gè)濱河市姑丑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辞友,老刑警劉巖栅哀,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異称龙,居然都是意外死亡留拾,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)鲫尊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)痴柔,“玉大人,你說(shuō)我怎么就攤上這事疫向】任担” “怎么了豪嚎?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)谈火。 經(jīng)常有香客問(wèn)我侈询,道長(zhǎng),這世上最難降的妖魔是什么糯耍? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任扔字,我火速辦了婚禮,結(jié)果婚禮上温技,老公的妹妹穿的比我還像新娘啦租。我一直安慰自己,他們只是感情好荒揣,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著焊刹,像睡著了一般系任。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上虐块,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天俩滥,我揣著相機(jī)與錄音,去河邊找鬼贺奠。 笑死霜旧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的儡率。 我是一名探鬼主播挂据,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼儿普!你這毒婦竟也來(lái)了崎逃?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤眉孩,失蹤者是張志新(化名)和其女友劉穎个绍,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體浪汪,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡巴柿,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了死遭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片广恢。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖呀潭,靈堂內(nèi)的尸體忽然破棺而出袁波,到底是詐尸還是另有隱情瓦阐,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布篷牌,位于F島的核電站睡蟋,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏枷颊。R本人自食惡果不足惜戳杀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夭苗。 院中可真熱鬧信卡,春花似錦、人聲如沸题造。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)界赔。三九已至丢习,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間淮悼,已是汗流浹背咐低。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留袜腥,地道東北人见擦。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像羹令,于是被迫代替她去往敵國(guó)和親鲤屡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359