4

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

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

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

当然,我可以通过调用或使用适当的参数在服务的客户端中很好地执行此操作ListenableFuture.addListener,但我的目标是精确地降低客户端代码中的复杂性和错误的可能性,所以我希望监听器当调用这些方法而不传递参数时,调用将在众所周知的情况下发生。Futures.addCallbackFutures.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 );
    }
}

首先,这是否有效?从 Guava 源来看,似乎确实如此,但我会很高兴得到某种确认,而且我在考虑对此进行单元测试时有点困难。

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

4

3 回答 3

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另一种可能性:当客户端调用addListener()不正确的回调时,您可以简单地记录警告,并super.addListener()使用正确的执行程序调用,而不是抛出一个。

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

于 2012-11-29T14:55:31.767 回答
1

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

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

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

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

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

于 2012-12-03T19:09:59.073 回答
0

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

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

于 2012-11-29T14:33:43.027 回答