天天看點

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰

一、Hook 線程介紹

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

先看一段示例代碼:

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰

①:為應用程式注入一個鈎子(Hook)線程,線程中,列印了相關日志,包括正在運作以及退出的日志;

②:再次注入一個同樣邏輯的鈎子(Hook)線程;

③:主線程執行結束,列印日志;

運作這段代碼,來驗證一下:

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰

從列印日志看到,當主線程執行結束,也就是 JVM 程序即将退出的時候,注入的兩個 Hook 線程都被啟動并列印相關日志。

二、Hook 線程的應用場景&注意事項

2.1 應用場景

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

羅列一些常見應用場景:

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

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰
PS: 這種防止程式重複執行的政策,也被應用于 Mysql 伺服器,zookeeper, kafka 等系統中。

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

2.2 注意事項

  • Hook 線程隻有在正确接收到退出信号時,才能被正确執行,如果你是通過 kill -9這種方式,強制殺死的程序,那麼抱歉,程序是不會去執行 Hook 線程的,為什麼呢?你想啊,它自己都被強制幹掉了,哪裡還管的上别人呢?
  • 請不要在 Hook 線程中執行一些耗時的操作,這樣會導緻程式長時間不能退出。

三、Hook 線程防應用重新開機實戰

針對上面防應用重新開機的場景,利用 Hook 線程,我們來實戰一下,貼上代碼:

import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
 * @author 小澤java
 * @date 2019/4/15
 * @time 下午3: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();
 }
}
</pre>           

運作程式,控制台輸出如下:

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰

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

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰

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

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰

可以看到,無法重複運作程式,且抛出了 The program already running. 的運作時異常。接下來,通過 kill pid 或者 kill -l pid 指令來結束程序:

面試官: 什麼是 Hook (鈎子) 線程以及應用場景?三、Hook 線程防應用重新開機實戰