如何表示Java Future结果的失败
如何表示Java Future结果的失败
据我所知,如果我想并行执行资源密集型代码,将Callable
/Runnable
提交给ExecutorService
是正确的方法。因此,我的方法结构如下:
public class ServiceClass { protected final ExecutorService executorService = Executors.newCachedThreadPool(); public FuturegetResult(Object params) { if (params == null) { return null; // 在这种情况下,方法应该失败 } // 进行其他快速预处理操作 return executorService.submit(new CallProcessResult(params)); } private class CallProcessResult implements Callable { private Object params; public CallProcessResult(Object params) { this.params = params; } @Override public Result call() throws Exception { // 根据给定的参数计算结果 // 失败也可能发生在这里! return result; } } } public class Result { ... }
上述代码中标记了两个可能发生错误的位置。对于这两种情况,可用的错误处理选项是不同的。
在提交任务之前,可能会出现无效参数、一些可能失败的快速预处理代码等问题。
我看到有几种方式可以表示失败:
- 如果给
getResult
提供了无效的params
,立即返回null。在这种情况下,每次调用getResult
时都需要检查是否返回了null。 - 抛出已检查异常而不是上述方法。
- 实例化一个在
get()
请求时返回null的Future
。我可以使用Apache Commons的ConcurrentUtils.constantFuture(null)
来实现。在这种情况下,我希望getResult
始终返回非空的Future
。我更喜欢这个选项,因为它与第二种情况一致。
在任务执行期间,我可能会遇到严重的错误,如内存不足、文件损坏、文件不可用等。
- 我认为在我的情况下,更好的选择是返回null,因为任务的结果是一个对象。
- 此外,我可以抛出已检查异常,并在
ThreadPoolExecutor.afterExecute
中处理它们(正如NiranjanBhat建议的)。参见Handling exceptions from Java ExecutorService tasks
在这两种情况下,哪种做法更好?
也许有其他方法或应该使用的设计模式?
当Java Future结果失败时,可以使用ThreadPoolExecutor类中定义的afterExecute方法来表示。这个方法在每个任务执行完成后被调用。在这个回调方法中,可以获取任务实例,并将错误记录在任务中的某个变量中,然后在这个方法中访问该变量。
然而,这种方法只适用于任务执行完成后的错误。如果任务在提交之前就发生了错误,就需要在ExecutorService之外进行处理,可以像上面所做的那样以一种简单的方式进行处理,或者可以将这个验证移动到任务代码本身中,这样所有的操作都在一个地方完成。
以下是一些示例和一些变体:
import java.util.concurrent.*; public class FutureExample { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(1); Futurefuture = executor.submit(new Callable () { public String call() throws Exception { // Simulate an error before the task is submitted throw new RuntimeException("Error before task submission"); } }); try { String result = future.get(); System.out.println("Result: " + result); } catch (ExecutionException e) { System.out.println("Task failed: " + e.getCause().getMessage()); } catch (InterruptedException e) { System.out.println("Task interrupted"); } executor.shutdown(); } }
这个示例中,我们在任务提交之前模拟了一个错误,并在任务执行完成后打印出错误信息。可以看到,即使在任务提交之前就发生了错误,也可以通过捕获ExecutionException来处理错误。
总结起来,如果想要表示Java Future结果的失败,可以使用ThreadPoolExecutor类中的afterExecute方法来处理任务执行完成后的错误。如果任务在提交之前就发生了错误,则需要在ExecutorService之外进行处理,可以通过捕获ExecutionException来处理这种情况。
如何表示Java Future结果的失败
在处理任务过程中,建议您简单地抛出适当的异常,而不在执行器中添加任何特殊处理。发生异常时,它将被捕获并存储在Future中。当调用Future的get方法时,它将抛出ExecutionException异常,调用get的调用者可以解包并处理该异常。这本质上是将正常的异常处理转换到Callable/Future范式中。代码示例如下:
FuturefutureResult = serviceClass.getResult("foo"); try { Result result = futureResult.get(); // do something with result } catch (ExecutionException ee) { Throwable e = ee.getCause(); // do something with e }
考虑到调用get的调用者必须具有对ExecutionException的处理,您可以利用这一点来处理提交期间的失败。为此,您可以构造一个类似于Apache Commons的constantFuture的Future,但是它会抛出给定的异常而不是返回给定的值。我不认为JDK中有这样的东西,但是编写它很简单(虽然有点繁琐):
public class FailedFutureimplements Future { private final Throwable exception; public FailedFuture(Throwable exception) { this.exception = exception; } public T get() throws ExecutionException { throw new ExecutionException(exception); } public T get(long timeout, TimeUnit unit) throws ExecutionException { return get(); } public boolean cancel(boolean mayInterruptIfRunning) { return false; } public boolean isCancelled() { return false; } public boolean isDone() { return true; } }
这有点不稳定-您正在将同步调用方法中的失败转换为异步调用方法中的失败。您将错误处理的负担从实际引起错误的代码转移到稍后运行的某些代码上。尽管如此,这确实意味着您可以将所有错误处理代码放在一个地方;这可能足以使其值得。
非常感谢您的建议,Tom。按照它们的建议是有道理的,因为无论如何都需要处理ExecutionException异常。我实际上不知道这一点。我想,为了避免编写FailedFuture的繁琐,我可以将方法的所有代码推迟到Callable中。
您可以这样做;这肯定会简化事情。不足之处在于,执行器线程必须选择任务并运行它,纯粹为了生成一个本可以立即产生的失败指示,这似乎有些浪费。