线程池使用FutureTask得不到抛出的错误
一、使用Thread
public static void main(String[] args) {
Test test = new Test();
FutureTask futureTask = new FutureTask<>(test);
Thread thread = new Thread(futureTask);
thread.start();
try {
futureTask.get();
}catch (Exception e){
e.printStackTrace();
}
}
public static class Test implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println(100 / 0);
return "test";
}
}
执行结果:
java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at com.jkys.drugstore.gwrpc.ActivityInfoServiceTest.main(ActivityInfoServiceTest.java:41)
Caused by: java.lang.ArithmeticException: / by zero
at com.jkys.drugstore.gwrpc.ActivityInfoServiceTest$Test.call(ActivityInfoServiceTest.java:66)
at com.jkys.drugstore.gwrpc.ActivityInfoServiceTest$Test.call(ActivityInfoServiceTest.java:62)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.lang.Thread.run(Thread.java:748)
二、使用线程池
public static void main(String[] args) {
getFuture();
}
public static void getFuture() {
ExecutorService executorService = Executors.newFixedThreadPool(1);
Future<?> future = executorService.submit(new FutureTask<>(() -> {
System.out.println(100 / 0);
return "test";
}));
try {
System.out.println(future.get());
} catch (Exception e) {
System.out.println("Error!");
}
}
执行结果: null,没有异常
objc[9813]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/bin/java (0x10098f4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1029c04e0). One of the two will be used. Which one is undefined.
null
三、解析
由上面两个例子可以看出,直接用Thread配合FutureTask是可以得到子线程中的异常的,那么为什么配合线程池却得不到呢?
3.1 线程池解析—从submit开始
//AbstractExecutorService 109行
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
//AbstractExecutorService 86行
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
//FutureTask 151行,也就是传进来的Runnable被包装成FutureTask
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
//Executors 404行
public static <T> Callable<T> callable(Runnable task, T result) {
if (task == null)
throw new NullPointerException();
return new RunnableAdapter<T>(task, result);
}
//Executors 503行
static final class RunnableAdapter<T> implements Callable<T> {
final Runnable task;
final T result;
RunnableAdapter(Runnable task, T result) {
this.task = task;
this.result = result;
}
public T call() {
task.run();
return result;
}
}
从上面代码部分能得到:线程池submit入参是Runnable,但是经过一系列的调用最终到了FutureTask的构造器,即把入参的Runnbale适配成一个新的FutureTask。 但是如果扔到线程池中的已经是一个FutureTask呢?其实也会被当做成Runnablue去继续被包装成FutureTask。
3.2包装的FutureTask和自己扔进去的FutureTask关系图
3.3调用时序图
由上面的关系图和时序图,就能得知为什么扔到线程池中的FutureTask在主线程get的时候得不到异常了。