Guava :为 ListenableFuture 回调和侦听器设置默认执行器

Tho*_*our 5 java future guava

我们的应用程序有许多实现ListenableFuture基于 API 的服务,包括:

public interface MyService {
    ListenableFuture<Thing> getMyThing();
    ListenableFuture<?> putMyThing(Thing thing);
}
Run Code Online (Sandbox Code Playgroud)

由于我们的域模型根本不是线程安全的,因此除了上述服务之外,我们的大部分代码都在众所周知的单线程上运行Executor。我认为如果服务能够保证添加到Future它们生成的 s 中的任何侦听器都将被调用,那就太好了Executor

当然,我可以通过调用 或使用适当的参数在服务的客户端中很好地强制执行此操作,ListenableFuture.addListener但我的目标正是降低客户端代码中Futures.addCallback的复杂性和错误的可能性,所以我想要侦听器当调用这些方法而不传递参数时,会发生众所周知的调用。Futures.transformExecutorExecutorExecutor

所以,现在我一直以这种方式实现服务的方法:

class MyServiceImpl {
    private Executor executor; /* the "main" executor */

    public ListenableFuture<Thing> getMyThing() {
        ListenableFuture<Thing> future = ...; /* actual service call */

        return Futures.transform(future, Functions.<Thing>identity(), executor );
    }
}
Run Code Online (Sandbox Code Playgroud)

首先,这有效吗?从番石榴来源来看,似乎确实如此,但我很高兴得到某种确认,并且我在考虑对此进行单元测试时遇到了一些困难。

此外,我有点担心整个“服务在指定线程上回调(默认情况下)”模式的有用性/成本比。有人有这样的经验吗?这种方法是否存在任何隐藏的陷阱?

Chr*_*irk 1

您提出的identity()解决方案应该可行——但有两个例外。

证明:首先,请注意,了解输入何时完成的唯一合理方法Futures.transform是通过调用input.addListener(). 我们知道addListener()尊重指定的执行人。(如果您不相信我:ListenableFutureTaskuses ExecutionList,它仅在两个地方调用侦听器:add()execute()。在这两个地方,它都使用给定的执行器。)因此,在输入Future完成时运行的任何任务都将在给定的执行器中运行。

这里的关键词是“在输入Future完成时运行”。例外情况:

  • 如果有人在 完成后添加监听器Future,它将在调用 的线程中运行addListener
  • 如果有人取消包装器Future(与原始包装器相反),侦听器将在调用 的线程中运行cancel

根据您的应用程序的结构,这些异常可能没问题,但需要牢记在心。如果它们是一个问题,您可能需要使用ForwardingListenableFuture解决方案(例如 eneveu 的解决方案,但显然不太严格)。