Guava:为ListenableFuture回调和监听器设置默认的Executor

时间:2012-11-29 11:26:29

标签: java guava future

我们的应用程序有许多服务可以实现基于ListenableFuture的API:

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

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

当然,我可以通过调用ListenableFuture.addListenerFutures.addCallbackFutures.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 );
    }
}

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

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

3 个答案:

答案 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()上执行,这意味着它们将在执行器上的所有相同的线程中执行,而不是扩散在同一个执行器中的线程之间可能应该这样。

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