面試官: 說說看, 什麼是 Hook (鉤子) 執行緒以及應用場景?

犬小哈發表於2019-04-11

Hook 鉤子執行緒
Hook 鉤子執行緒

文章首發自個人微訊號: 小哈學Java

個人網站地址: www.exception.site/java-concur…

目錄

  • 一、Hook 執行緒介紹

  • 二、Hook 執行緒的應用場景&注意事項

  • 三、Hook 執行緒防應用重啟實戰

  • 四、GitHub 原始碼地址

  • 五、總結

一、Hook 執行緒介紹

通常情況下,我們可以嚮應用程式注入一個或多個 Hook (鉤子) 執行緒,這樣,在程式即將退出的時候,也就是 JVM 程式即將退出的時候,Hook 執行緒就會被啟動執行

先看一段示例程式碼:

示例程式碼
示例程式碼

  • :為應用程式注入一個鉤子(Hook)執行緒,執行緒中,列印了相關日誌,包括正在執行以及退出的日誌;
  • :再次注入一個同樣邏輯的鉤子(Hook)執行緒;
  • :主執行緒執行結束,列印日誌;

執行這段程式碼,來驗證一下:

Hook 執行緒執行結果
Hook 執行緒執行結果

從列印日誌看到,當主執行緒執行結束,也就是 JVM 程式即將退出的時候,注入的兩個 Hook 執行緒都被啟動並列印相關日誌。

二、Hook 執行緒的應用場景&注意事項

2.1 應用場景

上面我們已經知道了, Hook 執行緒能夠在 JVM 程式退出的時候被啟動且執行,那麼,我們能夠通過這種特性,做點什麼呢?

羅列一些常見應用場景:

  1. 防止程式重複執行,具體實現可以在程式啟動時,校驗是否已經生成 lock 檔案,如果已經生成,則退出程式,如果未生成,則生成 lock 檔案,程式正常執行,最後再注入 Hook 執行緒,這樣在 JVM 退出的時候,執行緒中再將 lock 檔案刪除掉;

流程圖
流程圖

PS: 這種防止程式重複執行的策略,也被應用於 Mysql 伺服器,zookeeper, kafka 等系統中。

  1. Hook 執行緒中也可以執行一些資源釋放的操作,比如關閉資料庫連線,Socket 連線等。

2.2 注意事項

  1. Hook 執行緒只有在正確接收到退出訊號時,才能被正確執行,如果你是通過 kill -9這種方式,強制殺死的程式,那麼抱歉,程式是不會去執行 Hook 執行緒的,為什麼呢?你想啊,它自己都被強制幹掉了,哪裡還管的上別人呢?
  2. 請不要在 Hook 執行緒中執行一些耗時的操作,這樣會導致程式長時間不能退出。

三、Hook 執行緒防應用重啟實戰

針對上面防應用重啟的場景,利用 Hook 執行緒,我們來實戰一下,貼上程式碼:

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @author 犬小哈(微訊號: 小哈學Java)
 * @date 2019/4/10
 * @time 下午9:56
 * @discription
 **/
public class PreventDuplicated {

    /** .lock 檔案存放路徑 */
    private static final String LOCK_FILE_PATH = "./";
    
    /** .lock 檔名稱 */
    private static final String LOCK_FILE_NAME = ".lock";

    public static void main(String[] args) {

        // 校驗 .lock 檔案是否已經存在
        checkLockFile();

        // 注入 Hook 執行緒
        addShutdownHook();

        // 模擬程式一直執行
        for (;;) {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("The program is running ...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 注入 Hook 執行緒
     */
    private static void addShutdownHook() {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            // 接受到了退出訊號
            System.out.println("The program received kill signal.");
            // 刪除 .lock 檔案
            deleteLockFile();
        }));
    }

    /**
     * 校驗 .lock 檔案是否已經存在
     */
    private static void checkLockFile() {
        if (isLockFileExisted()) {
            // .lock 檔案已存在, 丟擲異常, 退出程式
            throw new RuntimeException("The program already running.");
        }

        // 不存在,則建立 .lock 檔案
        createLockFile();
    }

    /**
     * 建立 .lock 檔案
     */
    private static void createLockFile() {
        File file = new File(LOCK_FILE_PATH + LOCK_FILE_NAME);
        try {
            file.createNewFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * .lock 檔案 是否存在
     * @return
     */
    private static boolean isLockFileExisted() {
        File file = new File(LOCK_FILE_PATH + LOCK_FILE_NAME);
        return file.exists();
    }

    /**
     * 刪除 .lock 檔案
     */
    private static void deleteLockFile() {
        File file = new File(LOCK_FILE_PATH + LOCK_FILE_NAME);
        file.delete();
    }
}

複製程式碼

執行程式,控制檯輸出如下:

控制檯輸出
控制檯輸出

程式一直執行中,再來看下 .lock 檔案是否生成:

lock 檔案
lock 檔案

檔案生成成功,接下來,我們再次執行程式,看看是否能夠重複啟動:

重複啟動程式,丟擲異常
重複啟動程式,丟擲異常

可以看到,無法重複執行程式,且丟擲了 The program already running. 的執行時異常。接下來,通過 kill pid 或者 kill -l pid 命令來結束程式:

Hook 執行緒被啟動了
Hook 執行緒被啟動了

程式在即將退出的時候,啟動了 Hook 執行緒,在看下 .lock 檔案是否已被刪除:

.lock 檔案被刪除了
.lock 檔案被刪除了

到此,Hook 執行緒程式碼實戰部分結束了。

四、GitHub 原始碼地址

github.com/weiwosuoai/…

五、總結

本文中,我們學習了什麼是 Hook (鉤子) 執行緒,相關應用場景以及注意事項。祝你學習愉快 !

六、Ref

  • 《Java 高併發程式設計詳解》

歡迎關注微信公眾號: 小哈學Java

小哈學Java
小哈學Java

相關文章