为什么并行性ForkJoinPool加倍我的exception?

假设我有如下代码:

Future executeBy(ExecutorService executor) { return executor.submit(() -> { throw new IllegalStateException(); }); } 

使用ForkJoinPool#commonPool时没有问题,但是当我使用并行性ForkJoinPool它会使IllegalStateException加倍。 例如:

 executeBy(new ForkJoinPool(1)).get(); // ^--- double the IllegalStateException 

Q1 :为什么ForkJoinPoolException发生在Callable

Q2 :如何避免这种奇怪的行为?

如果在工作线程中抛出exception并将原始exception设置为其原因,则Fork / Join池通常会尝试在调用者的线程内重新创建exception。 这就是你所认为的“倍增”。 当您仔细观察堆栈跟踪时,您会注意到这两个exception之间的区别。

在这方面,共同池没有什么不同。 但是公共池允许调用者线程在等待最终结果时参与工作。 所以当你改变代码时

 static Future executeBy(ExecutorService executor) { return executor.submit(() -> { throw new IllegalStateException(Thread.currentThread().toString()); }); } 

你会注意到,调用者线程调用get()并在该方法中进行工作窃取比工作线程可以接收任务更快。 换句话说,您的供应商已在主/调用者线程中执行,在这种情况下,将不会重新创建exception。

通过抛出一个没有F / J可以使用的匹配公共构造函数的exception类型,可以很容易地禁用此function,就像这个整洁的内部类一样:

 static Future executeBy(ExecutorService executor) { return executor.submit(() -> { throw new IllegalStateException() { @Override public String toString() { String s = getClass().getSuperclass().getName(); String message = getLocalizedMessage(); return message!=null? s+": "+message: s; } }; }); } 

ForkJoinPool创建ForkJoinTask实例以执行您的提交。

ForkJoinTask尝试在发生exception时提供准确的堆栈跟踪。 它的javadoc说

Rethrownexception的行为与常规exception的行为相同,但是,如果可能,包含启动计算的线程以及实际遇到exception的线程的堆栈跟踪(例如使用ex.printStackTrace() ); 最低限度只有后者。

这是此行为的private实现中的注释

 /** * Returns a rethrowable exception for the given task, if * available. To provide accurate stack traces, if the exception * was not thrown by the current thread, we try to create a new * exception of the same type as the one thrown, but with the * recorded exception as its cause. If there is no such * constructor, we instead try to use a no-arg constructor, * followed by initCause, to the same effect. If none of these * apply, or any fail due to other exceptions, we return the * recorded exception, which is still correct, although it may * contain a misleading stack trace. * * @return the exception, or null if none */ private Throwable getThrowableException() { 

换句话说,它接受你的代码抛出的IllegalStateException ,找到接收ThrowableIllegalStateExceptionThrowable函数,调用具有原始IllegalStateException作为其参数的构造函数,并返回结果(然后在ExecutionException重新IllegalStateException )。

您的堆栈跟踪现在还包含get调用的堆栈跟踪。

使用ForkJoinPool作为您的ExecutorService ,我不相信您可以避免它,它取决于当前线程和抛出的exception类型中可用的构造函数是否抛出exception。