015-多线程-基础-Hook线程以及捕获线程执行异常

一、获取线程运行时异常

  在Thread类中有四个:

  public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh):为某个特定线程指定UncaughtExceptionHandler

  public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh):设置全局的UncaughtExceptionHandler

  public UncaughtExceptionHandler getUncaughtExceptionHandler():获取特定线程的UncaughtExceptionHandler

  public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler():获取全局的UncaughtExceptionHandler

1.1、UncaughtExceptionHandler

  线程在执行单元中是不允许抛出checked异常的,而且线程运行在自己的上下文中,派生他的线程将无法直接获得他运行中出现的异常信息。对此,java提供了UncaughtExceptionHandler接口,当线程在运行过程中出现异常时,会回调UncaughtExceptionHandler接口,从而得知哪个线程在运行时出错,以及出现什么样的错误。

    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

  UncaughtExceptionHandler是一个FunctionalInterface,只有一个抽象方法,该接口会被Thread的dispatchUncaughtException方法调用,如:

    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }

  当线程在运行过程中出现异常时,JVM会调用dispatchUncaughtException方法,该方法会将对应线程实例以及异常消息传递给回调接口

1.2、实例

    public static void main(String[] args) {
        Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
            System.out.println(t.getName() + " occur execption.");
            e.printStackTrace();
        });

        final Thread thread = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            System.out.println(1 / 0);
        }, "Test-Thread");
        thread.start();
    }

输出

Test-Thread occur execption.
java.lang.ArithmeticException: / by zero
    at com.github.bjlhx15.common.threaddemo

 

此种设计方式比较常见,如Google的guava toolkit的EventBus。

1.3、源码

比较简单,主要是线程出现异常→main Group→System Group→System.err

二、注入钩子线程

2.1、Hook线程

  通过Runtime可以为JVM进程退出时注入多个Hook线程。如下

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println("第1个hook,start");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第1个hook,end");
        }));
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println("第2个hook,start");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("第2个hook,end");
        }));
        System.out.println("程序结束。");
    }

输出:

程序结束。
第1个hook,start
第2个hook,start
第1个hook,end
第2个hook,end

 

2.2、实例

防止某个程序被重复启动,在进程启动时候创建一个lock文件,进程收到中断信号的时候会删除这个lock文件,比如mysql、zk、kafka等均能看到lock文件存在。

public class ThreadDemo11PreventDuplicatedHook {
    private final static String LOCK_PATH="/Users/lihongxu6/tmp/";
    private final static String LOCK_FILE=".lock";
    private final static String PERMISSIONS="rw-------";

    public static void main(String[] args) throws IOException {
        checkRunning();
        Runtime.getRuntime().addShutdownHook(new Thread(()->{
            System.out.println("The program received kill signal.");
            getLockFile().toFile().delete();
        }));
        while (true){
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println("program is running.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    private static void checkRunning() throws IOException {
        Path path=getLockFile();
        if(path.toFile().exists())
            throw new RuntimeException("The program already running.");
        Set<PosixFilePermission> perms= PosixFilePermissions.fromString(PERMISSIONS);
        Files.createFile(path,PosixFilePermissions.asFileAttribute(perms));
    }
    private static Path getLockFile(){
        return Paths.get(LOCK_PATH,LOCK_FILE);
    }
}

 

运行过程中会出现

-rw-------    1 lihongxu6 lihongsu\Domain Users         0 Feb 18 17:23 .lock

在程序正常退出或者kill pid、kill -1 pid之后,JVM进程会收到中断信号,并且启动hook线程删除.lock文件,

program is running.
The program received kill signal.

 2.3、注意事项

Hook线程只有在收到退出信号的时候被执行,如果在kill时候使用-9强杀进程,hook不会被执行。

hook线程一般用于一些资源释放工作,比如关闭句柄、socket连接、数据库的connection等

尽量不要做非常耗时操作

 

posted @ 2021-02-18 17:30  bjlhx15  阅读(225)  评论(0编辑  收藏  举报
Copyright ©2011~2020 JD-李宏旭