Java的Hook線程及捕獲線程執行異常

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;
import java.util.concurrent.TimeUnit;

public class Test {
    public static void main(String[] args){
//        CaptureThreadException.test();
//        EmptyException.test();
//        ThreadHook.test();
        PreventDuplicated.test();
    }
}

/*
    7.1 獲取線程運行時異常

        public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)
            :爲某個線程指定UncaughtExceptionHandler
        public UncaughtExceptionHandler getUncaughtExceptionHandler();
            :獲取某個線程的UncaughtExceptionHandler


        public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)
            :設置全局的UncaughtExceptionHandler
        public static void setDefaultUncaughtExceptionHandler(UncaughtException eh)
            :獲取全局的UncaughtExceptionHandler

    函數接口,該回調接口會被Thread中的dispatchUncaughtExcept方法調用,當線程在運行
    的過程當中出現了異常時,JVM會調用dispatchUncaughtExcept方法,該方法將對應的線程實
    例以及異常信息傳遞給回調接口:
        public interface UncaughtExceptionHandler{
            void uncaughtException(Thread t, Throwable e);
        }

        private void dispatchUncaughtExcept(Throwable e){
            getUncaughtExceptionHandler().uncaughtException(this.e);
        }

    閒談:
        在平時的工做中,這種設計方式是比較常見的,尤爲是那種異步執行方法,不如Google的
        guava toolkit就提供了EventBus,在EventBus中事件源和實踐的subscriber二者
        藉助於EventBus實現了徹底的解耦合,可是在subscriber執行任務時有可能會出現異常
        狀況,EventBus一樣也是藉助一個ExceptionHandler進行回調處理的。
 */
class CaptureThreadException{
    public static void test() {
        Thread.setDefaultUncaughtExceptionHandler((t,e)->{
            System.out.println(t.getName()+" occur exception");
            e.printStackTrace();
        });

        final Thread t = new Thread(()->{
            try{
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {

            }
            System.out.println(1/0);
        },"Test-Thread");

        t.start();
    }
}

/*
    7.1.3 UncaughtExceptionHandler源碼分析

    未注入UncaughtExceptionHandler回調接口的狀況下,就從ThreadGroup中獲取:
        1.該ThreadGroup若是有父ThreadGroup,則直接使用父Group的UncaughtException
        2.若是設置了全局默認的UncaughtExceptionHandler,則調用UncaughtException
        3.既沒有父,又沒有全局的,則直接將異常的堆棧信息定向到System.err中

    案例分析:
        因爲沒有指定,因此尋找過程爲:
            線程出現異常=>Main Group=>System Group=>System.err
 */

class EmptyException{
    public static void test() {
        ThreadGroup main = Thread.currentThread().getThreadGroup();
        System.out.println(main.getName());
        System.out.println(main.getParent());
        System.out.println(main.getParent().getParent());

        final Thread thread = new Thread(()->{
            try{
                TimeUnit.SECONDS.sleep(2);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(1/0);
        },"Test-Thread");

        thread.start();
    }
}

/*
    7.2.1 Hook線程介紹

    Jvm進程的退出是因爲JVM進程沒有活躍的非守護線程,或者系統中斷了信號。向JVM程序
    注入多個Hook線程,在JVM進程退出的時候,Hook線程會啓動執行,經過Runtime能夠爲
    JVM注入多個Hook線程。
 */

class ThreadHook{
    public static void test() {
        Runtime.getRuntime().addShutdownHook(new Thread(){
            public void run(){
                try{
                    System.out.println("The hook thread 1 is running...");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("The hook thread 1 will exit.");
            }
        });

        Runtime.getRuntime().addShutdownHook(new Thread(){
            public void run(){
                try{
                    System.out.println("The hook thread 2 is running...");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("The hook thread 2 will exit.");
            }
        });
    }
}

/*
    7.2.2 Hook線程實戰

    需求:
        在咱們開發中常常會遇到Hook線程,好比爲了防止某個程序被重複啓動,在進行啓動時
        會建立一個lock文件,進程收到中斷信號的時候會刪除這個lock文件,咱們在mysql服
        務器、zookeeper、kafka等系統中都能看到lock文件的存在,本節,將利用hook線程
        的特色,模擬一個防止重複啓動的線程。
 */
class PreventDuplicated {
    private final static String LOCK_PATH = "C:\\Users\\Administrator\\Desktop\\JavaAPI";
    private final static String LOCK_FILE = ".lock";
    private final static String PERMISSIONS = "rw-------";

    private static Path getLockFile(){
        return Paths.get(LOCK_PATH,LOCK_FILE);
    }

    private static void checkRunning(){
        Path path = getLockFile();

        if(path.toFile().exists()){
            throw new RuntimeException("The program already running...");
        }

        /*
            很尷尬一點,這個地方貌似是針對Linux的寫法,我這個地方出現了問題,
            暫時先將這個問題放一下吧。。。
         */
        Set<PosixFilePermission> perms = PosixFilePermissions.fromString(PERMISSIONS);

        try {
            Files.createFile(path,PosixFilePermissions.asFileAttribute(perms));
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void test(){
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                System.out.println("The program received kill SIGNAL.");
                getLockFile().toFile().delete();
            }
        });

        checkRunning();

        while (true) {
            try{
                TimeUnit.MILLISECONDS.sleep(1);
                System.out.println("program is running...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

/*
    7.2.3 Hook線程應用場景及注意事項
        1.Hook線程只有在收到退出信號的時候會被執行,若是kill使用了-9,那麼
            Hook線程不會執行,所以lock文件也不會被清理
        2.Hook線程中也能夠執行一些資源的釋放工做,如關閉文件句柄、socket連接
            、數據庫connection等
 */

 

《Java高併發編程詳解》筆記java

相關文章
相關標籤/搜索