基本
线程对于异常的处理:
public class ExceptionInRunnableTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
int i = 3 / 0;
});
thread.start();
}
}
运行上述代码:控制台输出如图。
当一个线程因为抛出异常,结束整个run方法时,结束整个thread生命周期时,JVM会在控制台打印异常信息。
当java的jar包跑在linux系统中时,就是会将异常信息输出到标准输出流里。
进阶
Thread有一个setUncaughtExceptionHandler方法,
可以改变线程结束抛出异常时JVM对应的动作。
public class ExceptionInRunnableTest {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
int i = 3 / 0;
});
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
long id = Thread.currentThread().getId();
System.out.println(id);
System.out.println(e);
}
});
thread.start();
}
}
运行上述代码:控制台输出如图:
可以发现此时输出的就不再时JVM原本输出的异常栈信息,而是我们定制的信息。
如果此时我们的代码写的不好导致异常信息没有被正确的记录,那么整个异常信息就会直接被吞掉,无法知道发生了什么。只能知道这个线程”诡异”的结束了。
如果此时我们代码是使用日志框架记录了异常:log.error(xxxx,e); 那么异常信息就会进入我们的日志文件,而不是由JVM输出到标准输出。
线程池
事实上在真实的java应用中,我们几乎很少会单独起一个线程做什么事儿,都是启动一个线程池。
此时我们不会主动new Thread,也就没地方去设置setUncaughtExceptionHandler方法。
那么想定制异常信息输出到日志文件,jdk的线程池提供了类似的方法:java.util.concurrent.ThreadPoolExecutor#afterExecute。作用和线程的setUncaughtExceptionHandler方法类似。也是用来定制处理线程结束时的逻辑。
但是这里有一个有趣的容易踩到的坑。如下的代码:
public class ExceptionPoolTest {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(1);
Future<?> submit = executor.submit(new Runnable() {
@Override
public void run() {
int i = 1 / 0;
}
});
}
}
运行后,不会输出任何异常信息。
为啥呢?因为我们在提交任务时,使用的subimit方法,这个方法是会返回Future结果的。所以一个线程就算是异常结束了,产生的异常也会被封装在Future里,只有在调用Future.get方法时才会抛出。
而上面的代码根本就不需要返回结果。所以也就没用到future。所以异常就感觉是莫名其妙的不见了。所以使用线程池时要注意。如果不打算返回任何结果,直接使用execute方法即可,不要使用submit。否则在生产环境找不到异常日志就难受了。