之前的博客中介绍过Runnable线程内部的异常处理,本文介绍线程池中线程异常的处理。

1)线程池中,UncaughtExceptionHandler机制是否仍然有效呢?

import java.util.concurrent.*;

public class Demo {
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

public static void main(String[] args) {
Future<Integer> future = executorService.submit(new MyTask());
try {
System.out.println("myTask任务执行结果为" + future.get());
} catch (InterruptedException e) {
System.out.println("任务被中断!");
} catch (ExecutionException e) {
System.out.println("任务内部抛出未受检异常!");
} catch (CancellationException e){
System.out.println("任务被取消!");
}
executorService.shutdown();
}

private static final class MyTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("unchecked exception happened:");
System.out.println(t.getId());
System.out.println(t.getName());
e.printStackTrace(System.out);
}
});
int sum = 0;
for (int i = 4; i >= 0; i--) {
sum = sum + (12 / i);
}
return sum;
}
}
}

运行上面的程序发现,我们自定义的UncaughtExceptionHandler没有生效。相反,Executor框架的异常捕获机制倒是生效了。这说明Callable已经不再使用UncaughtExceptionHandler机制类处理非受检异常了,而是使用它自己特有的机制。这一特有的机制是什么呢?

在线程池中,线程池Executor会Catch住所有运行时异常,当使用Future.get()获取其结果时,才会抛出这些运行时异常。我们看Future类的get()方法就可见一斑。

/**
* Waits if necessary for the computation to complete, and then retrieves its result.
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
*/
V get() throws InterruptedException, ExecutionException;
/**
* Waits if necessary for the computation to complete, and then retrieves its result.
* @return the computed result
* @throws CancellationException if the computation was cancelled
* @throws ExecutionException if the computation threw an exception
* @throws InterruptedException if the current thread was interrupted while waiting
*/
V get() throws InterruptedException, ExecutionException;

从JDK对Future.get()方法的定义可见:Callable线程中抛出的非受检异常会被Executor框架捕获到,然后通过Future类的get()方法传递给调用者。get() 方法的ExecutionException异常就是Runnable线程或者Callable线程抛出的异常。

2)那么问题来了,使用Runnable结合Executor框架是否也是同样的处理机制呢?还是说使用Runnable结合Executor就会使得UncaughtExceptionHandler生效呢?

来看下面的代码:

import java.util.concurrent.*;
public class Demo {
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

public static void main(String[] args) {
Future<?> future = executorService.submit(new MyTask());
try {
System.out.println("myTask任务执行结果为" + future.get());
} catch (InterruptedException e) {
System.out.println("任务被中断!");
} catch (ExecutionException e) {
System.out.println("任务内部抛出未受检异常!");
} catch (CancellationException e) {
System.out.println("任务被取消!");
}
executorService.shutdown();
}

private static final class MyTask implements Runnable {
@Override
public void run() {
Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("unchecked exception happened:");
System.out.println(t.getId());
System.out.println(t.getName());
e.printStackTrace(System.out);
}
});
int sum = 0;
for (int i = 4; i >= 0; i--) {
sum = sum + (12 / i);
}
}
}
}

程序运行的结果来看,Runnable结合Executor的情况下,仍然是和Callable的异常处理机制一样。我们可以这样总结:当使用Executor线程池相关框架来执行线程任务时,UncaughtExceptionHandler线程异常处理机制就是不生效的,这种情况下,线程内部的异常由线程池框架统一管理。当程序调用Future类的get()方法时,将感知到线程内部的运行时异常。

3)当程序不使用Future类获取结果时,运行时异常将被线程池框架隐藏。看下面的代码:

import java.util.concurrent.*;

public class Demo {
private static final ExecutorService executorService = Executors.newSingleThreadExecutor();

public static void main(String[] args) {
executorService.submit(new MyTask());

executorService.shutdown();
}

private static final class MyTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {

int sum = 0;
for (int i = 4; I >= 0; i--) {
sum = sum + (12 / i);
}
return sum;
}
}
}

此程序后,在控制台没有任何输出。可见不使用future.get()的时候,运行时异常被线程池框架给“吃了”。

既然是由线程池框架统一管理,那么是够可以修改修改线程池框架的异常处理机制呢?答案是可以的。此时我们要自定义线程池框架,使用ThreadPoolExecutor类即可。ThreadPoolExecutor类提供了很多可调整的参数和钩子函数,可以很方便的构造一个线程池。具体介绍参见笔者之前的博客​​ThreadPoolExecutor线程池的使用​​。在这篇博客中,介绍了一个afterExecute(Runnable, Throwable)方法,在该方法中可以定义当前线程池中执行的线程发生内部异常时的处理方案。例子如下:

import java.util.concurrent.*;
public class Demo {
private static final ExecutorService executorService = new ThreadPoolExecutor(1, 1, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10)) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
printException(r, t);
}

private void printException(Runnable r, Throwable t) {

if (t == null && r instanceof Future<?>) {
try {
Object result = ((Future<?>) r).get();
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt(); // ignore/reset
}
}
if (t != null) {
t.printStackTrace(System.out);
executeTask();
}
}
};

public static void main(String[] args) throws InterruptedException {
executeTask();
executorService.awaitTermination(5, TimeUnit.SECONDS);
executorService.shutdownNow();
}

private static void executeTask() {
executorService.submit(new MyTask());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private static final class MyTask implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 4; i >= 0; i--) {
sum = sum + (12 / i);
}
return sum;
}
}
}

在上面的代码中,我们使用ThreadPoolExecutor类自定义了一个线程池对象,对于所有使用该线程池执行的任务,其异常都会通过afterExecute()方法捕获到并进行处理。至此,关于Executor和Callable体系的线程内部运行时异常处理方案就介绍完了,你懂了吗!