下图:
线程在执行单元中是不允许抛出checked异常的,,而且线程运行在自己的上下文中,派生他的线程将无法直接获得它运行中出现的异常信息,对此Java给我们提供了一个uncaughtexceptionHandler接口,当线程在运行过程中出现异常时,就会回调UncaughtExceptionHandler接口,从而得知那个线程在运行中出错,以及出现了什么样的错误,如下
public static void main(String[] args) {
try{
Thread t =new Thread(new Runnable(){
@Override
public void run() {
int i = 10/0;
System.out.println("run....");
}
});
t.setUncaughtExceptionHandler(new UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("catch 到了"+t.getName());
e.printStackTrace();
}
});
t.start();
}catch(Exception e){
System.out.println("catch 不到");
}
}
结果如下:
public static void main(String[] args) {
try{
Thread t =new Thread(new Runnable(){
@Override
public void run() {
int i = 10/0;
System.out.println("run....");
}
});
t.start();
}catch(Exception e){
System.out.println("catch 不到");
}
}
二》 uncaughtExceptionHandker 源码分析
在没有向线程注入uncaughtExceptionHandler回调接口的时候线程若是出现了异常该怎么办?
get方法首先会判断当前线程中是否设置了handler,有则执行线程自己的uncaughtException 方法,否则就到 所在的threadgroup中获取,threadgroup同样也实现了uncaugthExceptionHandler接口,看看threadgroup里面的uncaught-Exception:
该threadgroup如果有父threadgroup,则直接调用父group的uncaughtException,
如果设置了全局默认的uncaughtexceptionHandler,则调用uncaughtException方法,
若既没有父threadgroup,也没有设置全局的默认uncaughtException,则会直接将异常的堆栈信息定向到system,err中
public static void main(String[] args) {
ThreadGroup maingroup=Thread.currentThread().getThreadGroup();
System.out.println(maingroup.getName());
System.out.println(maingroup.getParent());
System.out.println(maingroup.getParent().getParent());
final Thread thread=new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(1/0);
},"test-thread");
thread.start();
}
上面代码没有设置默认的handler,也没有对thread指定handler,因此当thread出现异常的时候,会向上寻找group的uncaughtException,如图:
三 注入钩子线程:
hook线程:
jvm的退出是由于jvm进程中没有活跃的非守护线程,或者收到了系统的中断信号,向jvm程序中注入了一个Hook线程,在jvm进城退出的时候,Hook线程会启动执行,通过Runtime可以为jvm注入多个Hook线程,例子:
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
System.out.println("the hook thread 1 is running.");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("the hook thread 1 will exit.");
}
});
//可以注入多个
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
try {
System.out.println("the hook thread 2 is running.");
TimeUnit.SECONDS.sleep(1);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("the hook thread 2 will exit.");
}
});
}
java 注入了两个Hook线程,在main线程中结束,也就是jvm中没有了活动的非守护线程,jvm即将推出的时候,两个Hook线程会被启动并且运行,结果如下:
在我们开发中经常会遇到Hook线程,比如为了防止某个程序被重复启动,在进程启动时会创建一个lock文件,进程收到中断信号的时候会删除这个lock文件的存在, 模拟一个利用Hook线程防止重复启动的程序,;
public class ThreadGroupCreator {
private final static String LOCK_PATH="/wenjian/home";
private final static String LOCK_FILE=".lock";
private final static String PERMISSIONS="rw-------";
public static void main(String[] args) throws IOException{
Runtime.getRuntime().addShutdownHook(new Thread(()->{
System.out.println("the program received kill signal.");
getLockFile().toFile().delete();
}));
checkckRunning();
for(; ;) {
try {
TimeUnit.MILLISECONDS.sleep(1);
System.out.println("program is running.");
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static void checkckRunning() 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 java.nio.file.Paths.get(LOCK_PATH, LOCK_FILE);
}
}
运行发现多了个.lock文件
执行kill pid jvm收到中断信号,并且启动Hook线程,删除。,lock 文件 输出如下
Hook线程只会在收到退出信号的时候会被执行,如果在kill的时候使用了参数-9,那么Hook线程不会得到执行,进程会立即退出,因此。lock文件得不到清理
Hook线程中也可以执行一些资源释放的工作,比如关闭文件句柄,socket 连接。数据库的connection等
尽量不要再Hook线程中执行一些非常耗时非常浪费时间的操作,会导致程序迟迟不能退出