Thread和UncaughtExceptionHandler
相信现在大家在写java程序时,必然会接触到多线程的概念。多线程很强大,但是也很容易出错。其中一个经常被忽略的错误就是线程无故异常退出,这种情况多数是因为某些未被捕获的异常直接抛出导致的,而且这时候如果处理不当,可能会导致系统资源的泄露,比如数据库连接未释放等。
先看下面的例子:
Runnable runnable = new Runnable() {
@Override
public void run() {
int a = 1 / 0;
System.out.println(a);
}
};
new Thread(runnable).start();
上面的代码中,执行到1/0
的时候必然会抛出异常,导致下面的语句没法执行。虽然run接口不抛出任何受检异常,但是确可能抛出未受检异常从而导致执行线程的退出。为了防止线程无声的退出,我们可以在代码中用try{...}catch(Throwable t){}
的方法来讲所有的异常捕获。另一种方法是利用Thread提供的UncaughtExceptionHandler
来处理:
Runnable runnable = new Runnable() {
@Override
public void run() {
int a = 1 / 0;
System.out.println(a);
}
};
Thread thread = new Thread(runnable);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(String.format("Exception got - doing something, thread=%s, errMsg=%s", t.getName(), e.getMessage()));
}
});
thread.start();
执行结果:
Exception got - doing something, thread=Thread-0, errMsg=/ by zero
在上面的代码中,我们为线程指定了一个Handler来处理未被捕获的异常,这种做法只会对该线程有限,其他的线程抛出的未受检异常则不会被处理。Thread中可以设置一个默认的UncaughtExceptionHandler,这样可以将该异常处理句柄应用到大部分的线程上。
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
// do something
}
});
Executor框架和UncaughtExceptionHandler
使用Thread.setDefaultUncaughtExceptionHandler
Executor框架是最常用的一个线程框架,那么在线程池中,如果线程抛出的异常未被捕获,同样会导致工作线程的退出,线程池会根据情况,确定是否起新的线程来代替该工作线程。前面提到的通过Thread.setDefaultUncaughtExceptionHandler
同样对通过线程池创建的线程起作用:
Runnable runnable = new Runnable() {
@Override
public void run() {
int a = 1 / 0;
System.out.println(a);
}
};
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Uncaught exception, thread=" + t.getName() + ", exception=" + e.getMessage());
}
});
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 2; i++) {
executorService.execute(runnable);
}
执行结果:
Uncaught exception, thread=pool-1-thread-1, exception=/ by zero
Uncaught exception, thread=pool-1-thread-2, exception=/ by zero
利用ThreadFactory
还有一种方法就是利用ThreadFactory,我们在创建线程池的时候指定一个ThreadFactory,该工厂负责为每个由其创建线程设置UncaughtExceptionHandler。
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Start");
int a = 1 / 0;
System.out.println(a);
}
};
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Uncaught exception, thread=" + t.getName() + ", exception=" + e.getMessage());
}
};
AtomicInteger threadCount = new AtomicInteger();
ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "thread-" + threadCount.incrementAndGet());
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
});
for (int i = 0; i < 2; i++) {
executorService.execute(runnable);
}
执行结果:
Start
Uncaught exception, thread=pool-1-thread-1, exception=/ by zero
Start
Uncaught exception, thread=pool-1-thread-2, exception=/ by zero
请注意submit(Runnable)
但是,即使你设置了UncaughtExceptionHandler,在线程池中也可能会遇到异常未被处理的情况。我们把上面的代码中线程池execute()
改为submit()
再执行一次:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Start");
int a = 1 / 0;
System.out.println(a);
}
};
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Uncaught exception, thread=" + t.getName() + ", exception=" + e.getMessage());
}
};
AtomicInteger threadCount = new AtomicInteger();
ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "thread-" + threadCount.incrementAndGet());
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
});
for (int i = 0; i < 2; i++) {
executorService.execute(runnable);
}
输出:
Start
Start
可以发现,这时候异常并没有被UncaughtExceptionHandler处理。这是因为对于submit执行的任务,task产生的未处理的异常都会存在Future对象中作为执行的一种结果。我们可以通过Future#get(),如果有异常会被封装成ExecutionException
抛出来:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("Start");
int a = 1 / 0;
System.out.println("End");
}
};
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("Uncaught exception, thread=" + t.getName() + ", exception=" + e.getMessage());
}
};
AtomicInteger threadCount = new AtomicInteger();
ExecutorService executorService = Executors.newSingleThreadExecutor(new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r, "thread-" + threadCount.incrementAndGet());
thread.setUncaughtExceptionHandler(uncaughtExceptionHandler);
return thread;
}
});
for (int i = 0; i < 2; i++) {
Future future = executorService.submit(runnable);
try {
future.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
System.out.println("Exception from future " + e.getCause().getMessage());
}
}
运行结果:
Start
Exception from future / by zero
Start
Exception from future / by zero
ScheduledExecutorService的execute和submit
这里还有个特例,ScheduledExecutorService
的execute方法其实也是调用的submit,所以就算你调用ScheduledExecutorService#submit
,任务中抛出的异常也不会走到UncaughtExceptionHandler中去。
// ScheduledThreadPoolExecutor#execute的源码
/**
* Executes {@code command} with zero required delay.
* This has effect equivalent to
* {@link #schedule(Runnable,long,TimeUnit) schedule(command, 0, anyUnit)}.
* Note that inspections of the queue and of the list returned by
* {@code shutdownNow} will access the zero-delayed
* {@link ScheduledFuture}, not the {@code command} itself.
*
* <p>A consequence of the use of {@code ScheduledFuture} objects is
* that {@link ThreadPoolExecutor#afterExecute afterExecute} is always
* called with a null second {@code Throwable} argument, even if the
* {@code command} terminated abruptly. Instead, the {@code Throwable}
* thrown by such a task can be obtained via {@link Future#get}.
*
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution because the
* executor has been shut down
* @throws NullPointerException {@inheritDoc}
*/
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
总结
线程的异常退出可能会导致一些诡异的错误,应该尽量保证异常都被处理到。UncaughtExceptionHandler
是一种有效的手段,但是要注意在和Executor框架结合是,submit的task产生的未捕获的异常不会被注册的UncaughtExceptionHandler
处理,需要通过Future#get
来处理。需要注意的是ScheduledExecutorService#execute
其实是调用的submit方法。
「真诚赞赏,手留余香」
真诚赞赏,手留余香
使用微信扫描二维码完成支付