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

时间:2022-01-25 20:18:44

assuming I have the code like as below:

假设我有如下代码:

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

there is no problem when using ForkJoinPool#commonPool, but when I using a parallelism ForkJoinPool it will double the IllegalStateException. for example:

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

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

Q1: why the parallelism ForkJoinPool double the Exception occurs in the Callable?

Q1:为什么兼容性ForkJoinPool将Exception发生在Callable中?

Q2: how to avoiding this strange behavior?

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

2 个解决方案

#1


10  

The Fork/Join pool generally attempts to recreate the exception within the caller’s thread if the exception has been thrown in a worker thread and sets the original exception as its cause. This is what you perceived as “doubling”. When you look closer at the stack traces, you will notice the difference between these two exceptions.

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

The common pool is not different in that regard. But the common pool allows the caller thread to participate in the work when waiting for the final result. So when you change you code to

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

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

you will notice that it often happens that the caller thread is faster in calling get() and do work-stealing within that method than a worker thread can pick up the task. In other words, your supplier has been executed within the main/caller thread and in this case, the exception will not be recreated.

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

This feature can easily disabled by throwing an exception type which has no matching public constructor the F/J could use, like with this neat inner class:

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

static Future<Object> 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;
                }
            };
    });
}

#2


8  

The ForkJoinPool creates ForkJoinTask instances to execute your submissions.

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

ForkJoinTask tries to provide an accurate stack trace when exceptions occur. Its javadoc states

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

Rethrown exceptions behave in the same way as regular exceptions, but, when possible, contain stack traces (as displayed for example using ex.printStackTrace()) of both the thread that initiated the computation as well as the thread actually encountering the exception; minimally only the latter.

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

This is the comment in the private implementation of this behavior

这是此行为的私有实现中的注释

/**
 * 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() {

In other words, it takes the IllegalStateException your code threw, finds a constructor of IllegalStateException that receives a Throwable, invokes that constructor with the original IllegalStateException as its argument, and returns the result (which is then rethrown within a ExecutionException).

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

Your stack trace now also contains the stack trace for the get call.

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

With ForkJoinPool as your ExecutorService, I don't believe you can avoid it, it's dependent on if the exception was not thrown by the current thread and the constructors available in the thrown exception type.

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

#1


10  

The Fork/Join pool generally attempts to recreate the exception within the caller’s thread if the exception has been thrown in a worker thread and sets the original exception as its cause. This is what you perceived as “doubling”. When you look closer at the stack traces, you will notice the difference between these two exceptions.

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

The common pool is not different in that regard. But the common pool allows the caller thread to participate in the work when waiting for the final result. So when you change you code to

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

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

you will notice that it often happens that the caller thread is faster in calling get() and do work-stealing within that method than a worker thread can pick up the task. In other words, your supplier has been executed within the main/caller thread and in this case, the exception will not be recreated.

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

This feature can easily disabled by throwing an exception type which has no matching public constructor the F/J could use, like with this neat inner class:

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

static Future<Object> 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;
                }
            };
    });
}

#2


8  

The ForkJoinPool creates ForkJoinTask instances to execute your submissions.

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

ForkJoinTask tries to provide an accurate stack trace when exceptions occur. Its javadoc states

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

Rethrown exceptions behave in the same way as regular exceptions, but, when possible, contain stack traces (as displayed for example using ex.printStackTrace()) of both the thread that initiated the computation as well as the thread actually encountering the exception; minimally only the latter.

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

This is the comment in the private implementation of this behavior

这是此行为的私有实现中的注释

/**
 * 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() {

In other words, it takes the IllegalStateException your code threw, finds a constructor of IllegalStateException that receives a Throwable, invokes that constructor with the original IllegalStateException as its argument, and returns the result (which is then rethrown within a ExecutionException).

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

Your stack trace now also contains the stack trace for the get call.

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

With ForkJoinPool as your ExecutorService, I don't believe you can avoid it, it's dependent on if the exception was not thrown by the current thread and the constructors available in the thrown exception type.

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