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



我们的应用程序有许多实现基于ListenableFuture的API的服务,其调优为:

public interface MyService {
ListenableFuture<Thing> getMyThing();
ListenableFuture<?> putMyThing(Thing thing);
}

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

当然,通过使用适当的Executor参数调用ListenableFuture.addListenerFutures.addCallbackFutures.transform,我可以很好地在服务的客户端中强制执行这一点,但我的目标是精确地降低客户端代码中出错的复杂性和可能性,因此我希望在不传递Executor参数的情况下调用这些方法时,侦听器调用发生在众所周知的Executor上。

因此,目前我一直以这种方式实现服务的方法:

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 );
}
}

首先,这行得通吗?从Guava的消息来源来看,似乎确实如此,但我很高兴得到某种证实,我很难考虑单元测试。

此外,我有点担心整个"Service callback on specified thread(默认情况下)"模式的有用性/成本比。有人有这样的经历吗?这种方法是否存在隐藏的陷阱?

难道你不能让所有的方法都返回一个ListenableFuture包装器吗

import com.google.common.util.concurrent.ForwardingListenableFuture;
import com.google.common.util.concurrent.ListenableFuture;
import javax.annotation.Nonnull;
import java.util.concurrent.Executor;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
public class ListenableFutureWithFixedExecutor<V> extends ForwardingListenableFuture<V> {
@Nonnull
private final ListenableFuture<V> delegate;
@Nonnull
private final Executor executor;
public ListenableFutureWithFixedExecutor(@Nonnull ListenableFuture<V> delegate, @Nonnull Executor executor) {
this.delegate = checkNotNull(delegate);
this.executor = checkNotNull(executor);
}
@Override
protected ListenableFuture<V> delegate() {
return delegate;
}
@Override
public void addListener(Runnable listener, Executor executor) {
checkArgument(this.executor.equals(executor), "Listeners can only be executed using %s", executor);
super.addListener(listener, executor);    //To change body of overridden methods use File | Settings | File Templates.
}
}

另一种可能性是:当客户端使用不正确的回调调用addListener()时,您可以简单地记录一个警告,并使用正确的执行器调用super.addListener(),而不是抛出IllegalArgumentException

您甚至可以添加一个方法<V> void addCallback(FutureCallback<? super V> callback),使用固定的执行器将给定的回调添加到Future。这会让你更流利地使用你的未来:)

您提出的identity()解决方案应该可以工作——只有两个例外。

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

这里的关键短语是"在输入Future完成时运行">

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

根据应用程序的结构,这些异常可能是可以的,但需要记住。如果它们是一个问题,您可能需要使用ForwardingListenableFuture解决方案(类似于eneveu的解决方案,但显然不那么严格)。

我认为您在以下方面遇到了一个陷阱:侦听器将在sameThreadExecutor()上执行,这意味着它们将在中执行执行器上的所有相同线程,而不是像应该的那样在同一执行器中的线程之间展开。

我不相信会有一种方便的方式来做你想做的事情。

最新更新