我们的应用程序有许多实现基于ListenableFuture
的API的服务,其调优为:
public interface MyService {
ListenableFuture<Thing> getMyThing();
ListenableFuture<?> putMyThing(Thing thing);
}
由于我们的域模型根本不是线程安全的,除了前面提到的服务之外,我们的大部分代码都在一个众所周知的单线程Executor
上运行。我认为,如果服务能够保证添加到它们生成的Future
的任何侦听器都将在该Executor
上被调用,那就太好了。
当然,通过使用适当的Executor
参数调用ListenableFuture.addListener
、Futures.addCallback
或Futures.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()
上执行,这意味着它们将在中执行执行器上的所有相同线程,而不是像应该的那样在同一执行器中的线程之间展开。
我不相信会有一种方便的方式来做你想做的事情。