Java高并发编程之Hook线程以及捕获线程执行异常

7.1 获取线程运行时异常

在Thread类中,关于处理运行时异常的API总共有四个,如下:

public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        checkAccess();
        uncaughtExceptionHandler = eh;
    }
    .................
    public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(
                new RuntimePermission("setDefaultUncaughtExceptionHandler")
                    );
        }

         defaultUncaughtExceptionHandler = eh;
     } 
     ........................
   public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }   
    ..........................
   public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
        return defaultUncaughtExceptionHandler;
    } 

7.1.1 UncaughtExceptionHandler的介绍

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

public class CaptureThreadException {
 public static void main(String[] args) {
	//设置回调接口
	 Thread.setDefaultUncaughtExceptionHandler((t,e)->{
		 System.out.println(t.getName()+"occur exception");
		 e.printStackTrace();
	 });
	 final Thread thread = new Thread(()->{
		 try {
			TimeUnit.SECONDS.sleep(2);
		} catch (InterruptedException e1) {
			
			e1.printStackTrace();
		}
		 System.out.println(1/0);
		 
	 },"Test-Thread");
	 thread.start();
  }
}
......................
java.lang.ArithmeticException: / by zero
Test-Threadoccur exception
	at Test/cn.yu.uncaughtexceptionhandler.CaptureThreadException.lambda$1(CaptureThreadException.java:19)
	at java.base/java.lang.Thread.run(Thread.java:835)

7.1.2 UncaughtExceptionHandler源码分析

Thread类的源码

 public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }  

getUncaughtExceptionHandler方法首先会判断当前线程是否设置了Handler,如果有则执行线程自己的uncaughtException方法,否则就到所在的ThreadGroup中获取,ThreadGroup同样也实现了UncaughtExceptionHandler接口

 public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }

1.该ThreadGroup如果有父ThreadGroup,则直接调用父Group的uncaughtException方法
2.如果设置了全局默认的UncaughtExceptionHandler,则调用uncaughtException方法
3.若既没有父HTreadGroup,也没有设置全局默认的UncaughtExceptionHandler,则会直接将异常的堆栈信息定向到System.err中

7.2 注入钩子线程

7.2.1 Hook线程介绍

JVM进程的退出是由于进程中没有活跃的非守护线程,或者受到了系统中断信号,想JVM程序注入一个HooK线程,在JVM进程退出的时候,Hook线程会启动执行,通过向Runtime可以为JVM注入多个Hook线程

public class ThreadHook {

	public static void main(String[] args) {
		    //钩子线程1
          Runtime.getRuntime().addShutdownHook(new Thread() {
        	  public void run() {
        		  System.out.println("The hook thread 1 is running");
        		  try {
					TimeUnit.MICROSECONDS.sleep(1);
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
        		  System.out.println("The hook thread 1 will exit.");
        	  }
        	  
          });
          
          Runtime.getRuntime().addShutdownHook(new Thread() {
        	  public void run() {
        		  System.out.println("The hook thread 2 is running");
        		  try {
					TimeUnit.MICROSECONDS.sleep(1);
				} catch (InterruptedException e) {
					
					e.printStackTrace();
				}
        		  System.out.println("The hook thread 2 will exit.");
        	  }
        	  
          });
          
          System.out.println("The program will is stopping.");
          
	}

}
...............................
The program will is stopping.
The hook thread 1 is running
The hook thread 2 is running
The hook thread 2 will exit.
The hook thread 1 will exit.

在上述代码中,给Java程序注入了两个Hook线程,在main线程中结束,也就是JVM中没有了活动的非守护线程,JVM进程即将退出时,两个Hook线程会被启动并运行。

7.2.2 Hook线程实战

public class PreventDuplicated {
  private final static String Lock_Path = "/Test/";
  private final static String LOCK_FILE = ".lock";
  private final static String PERMISSIONS = "rw-----------";
   public static void main(String[] args) throws InterruptedException, IOException {
	   //注入Hook线程,在程序退出时,删除lock文件
	Runtime.getRuntime().addShutdownHook(new Thread(()->{
		System.out.println();
		getLockFile().toFile().delete();
	}) );
	//检查是否存在.lock文件
	checkRunning();
	//简单模拟当前程序正在运行
	for(;;) {
		TimeUnit.MILLISECONDS.sleep(1);
		System.out.println("程序正在运行");
	}
}
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);
}
}

7.2.3 Hook线程应用场景以及注意事项

1.Hook线程只有在收到退出信号的时候会被执行,如果在kill的时候使用了参数-9,那么Hook线程不会得到执行,进程将会立即退出,一次.lock文件将得不到清理
2.Hook线程中也可以执行一些资源释放的工作,比如关闭文件句柄,socket链接,数据库connection等
3.尽量不要在Hook线程中执行一些耗时非常长的操作,因为其会导致导致程序迟迟不能退出。

本章总结:

本章介绍了通过 UncaughtExceptionHandler回调的方式获取线程运行期间的异常信息。
Hook线程是一个非常好的机制,以帮助程序获得进程中断信号,有机会在进程退出之前做一些资源释放的动作,或者做一些告警通知。切记如果强制杀死进程,那么进程将不会收到任何中断信号。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值