我们的应用程序有许多服务可以实现基于ListenableFuture
的API:
public interface MyService {
ListenableFuture<Thing> getMyThing();
ListenableFuture<?> putMyThing(Thing thing);
}
由于我们的域模型完全不是线程安全的,因此我们在众所周知的单线程Executor
上运行除了上述服务之外的大部分代码。我认为如果服务能够保证添加到他们生成的Future
的任何监听器都将在Executor
上调用,那将是很好的。
当然,我可以通过调用ListenableFuture.addListener
,Futures.addCallback
或Futures.transform
并使用相应的Executor
参数,在服务的客户端强制执行此操作,但我能做什么?目标是精确降低客户端代码中的复杂性和错误的可能性,所以我希望在调用这些方法而不传递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 );
}
}
首先,这是否有效?从番石榴来源看来,似乎确实如此,但我会很高兴得到一些确认,而且我在考虑进行单元测试时遇到了一些困难。
此外,我有点担心整个“服务回调指定线程(默认情况下)”模式的有用性/成本比。有没有人有这样的经历?这种方法有任何隐藏的陷阱吗?
答案 0 :(得分:2)
你能不能让所有方法返回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.
}
}
另一种可能性:当客户端使用不正确的回调调用IllegalArgumentException
时,您可以简单地记录警告,并使用正确的执行程序调用{{1}},而不是抛出addListener()
。
您甚至可以添加一个方法super.addListener()
,使用固定的执行程序将给定的回调添加到Future。这可以让你更流利地使用你的未来:)
答案 1 :(得分:1)
您提出的identity()
解决方案应该有效 - 除了两个例外。
证明:首先,请注意Futures.transform
在输入完成时学习的唯一合理方法是通过调用input.addListener()
。我们知道addListener()
尊重给定的执行者。 (如果你不相信我:ListenableFutureTask
使用ExecutionList
,它只在两个地方调用听众:add()
和execute()
。在这两个地方,它使用给定的执行者。)因此,在输入Future
完成时运行的任何任务都将在给定的执行程序中运行。
此处的关键短语是“在输入Future
完成时运行。”例外:
Future
完成后添加了一个监听器,它将在调用addListener
的线程中运行。Future
(而不是原始包装器),则侦听器将在调用cancel
的线程中运行。根据应用程序的结构,这些异常可能没问题,但要记住这些异常。如果它们是一个问题,你可能想要使用ForwardingListenableFuture
解决方案(如eneveu's但显然不那么严格)。
答案 2 :(得分:0)
我认为你有以下方式的陷阱:监听器将在sameThreadExecutor()
上执行,这意味着它们将在执行器上的所有相同的线程中执行,而不是扩散在同一个执行器中的线程之间可能应该这样。
我不相信会有一种方便的方式来做你想做的事。