【问题标题】:How do I intercept and handle a Future in a decorator class?如何在装饰器类中拦截和处理 Future?
【发布时间】:2013-10-01 18:40:23
【问题描述】:

我正在装饰一个类,其中一些方法返回一个 Future。在一种特定情况下,我对 Future 的结果感兴趣,并想在通知调用者之前拦截它。也许是这样的:

public Future<Integer> getCalculation() {
  Future<Integer> future = realObject.getCalculation();
  // Get the result, store it somewhere
  // ???
}

如果 getCalculation 是同步的,我可以调用 realObject,等待响应,对它做任何我想做的事情,然后简单地返回它,如下所示:

public int getCalculation() {
  int result = realObject.getCalculation();
  this.cache = result;
  return result;
}

如何将此模式应用于返回 Future 的东西?

【问题讨论】:

  • 如果您想在返回之前获取并存储结果,那么使用Future 的意义何在?您不妨现在就返回int
  • 在我的特殊情况下,我想在让客户看到之前捕获结果的副本。我希望以异步方式进行计算,但我希望首先了解结果。
  • 对...所以您仍然可以使用 future 来异步它,但是如果您在将结果交给客户端之前捕获结果,您不妨返回 int。我会发布一个答案来尝试澄清这一点。

标签: java multithreading decorator future


【解决方案1】:

来自 Guava 库的SettableFuture 可能会派上用场: http://docs.guava-libraries.googlecode.com/git/javadoc/com/google/common/util/concurrent/SettableFuture.html

您确实可以创建一个SettableFuture 并为其分配您的直接价值。

【讨论】:

    【解决方案2】:

    如何将此模式应用于返回 Future 的东西?

    我认为你必须在这里实现自己的代理Future 类。可能类似于以下委托给代理的内容:

    public class FutureProxy implements Future<Integer> {
        private Calculator calculator;
        private Future<Integer> delegate;
        public FutureProxy(Calculator calculator, Future<Integer> delegate) {
            this.calculator = calculator;
            this.delegate = delegate;
        }
        // proxy all of the methods
        public boolean isDone() {
            return delegate.isDone();
        }
        ...
        // for get, you can then call back to `calculator`
        public Integer get() throws InterruptedException, ExecutionException {
            Integer result = delegate.get();
            calculator.setCachedCalculation(result);
            return result;
        }
        // need to handle get(long, TimeUnit) as well
        public Integer get(long timeout, TimeUnit unit)
              throws InterruptedException, ExecutionException, TimeoutException;
            Integer result = delegate.get(timeout, unit);
            calculator.setCachedCalculation(result);
            return result;
        }
    }
    

    那么你的装饰器吸气剂看起来像:

    public Future<Integer> getCalculation() {
       Future<Integer> future = realObject.getCalculation();
       return new FutureProxy(this, future);
    }
    

    正如您在 cmets 中提到的,如果 FutureProxy 是装饰器的内部类,则不会注入 Calculator。如果代理正在更新 Calculator 中将被其他线程访问的字段,请确保您使用 volatile 字段。

    【讨论】:

    • 我绝对不希望装饰器阻塞。后台线程是一个想法,但我不希望每个 Future 都有一个线程——这会使系统必须执行的线程数量翻倍。
    • 这个装饰器正在装饰一个我无法控制的类。我无法更改父母的返回类型。
    • 另外:调用者会假设他们总是得到一个 Future 并且它遵守 Future 合约。
    • 如果FutureProxy 是装饰器的内部类,则无需将装饰器的引用传递给代理。我认为这是迄今为止最优雅的答案。谢谢!
    • 好点@bstempi。我忘了提到,如果要在不同的线程中访问,您需要将更新的字段设置为 volatile
    【解决方案3】:

    在这种情况下,你的方法必须阻塞...

    public Future<Integer> getCalculation() {
      Future<Integer> future = realObject.getCalculation();
    
      // Get the result, store it somewhere
      //the .get() method is blocking...
      myInteger = future.get();
    
      return future;
    }
    

    调用者代码会立即成功get()-ting结果...

    编辑

    如果需要非阻塞,为将来创建一个包装类

    FutureWrapper<T> implements Future<T> {
    
        Future<T> wrapped;
        public FutureWrapper(Future<T> toBeWrapped) {
            wrapped = toBeWrapped;
        }
    
        public T get() {
            return wrapped.get();
        }
    
        //the other wrapper methods left out for sake of clarity
    }
    
    
    MyWrapper extends FutureWrapper<Integer>{
    
        //100% boilerplate
        public MyWrapper(Future<Integer> toBeWrapped) {
            super(toBeWrapped);
        }
    
        @Override
        public Integer get() {
            Integer value = super.get();
            //do the dirty stuff
            return value;
        }
    
    }
    

    EDIT2

    如果“肮脏的东西”涉及抚摸装饰器的隐私(对不起,不是英语母语:)(双关语),您可以在装饰器类中定义 MyWrapper 类:

    public MyDecorator {
    
        Integer dirtyStuff;
    
    
        public class MyWrapper extends FutureWrapper<Integer>{
            //boilerplate omitted
            public Integer get() {
                dirtyStuff= super.get();
                return dirtyStuff;
            }
    }
    

    【讨论】:

    • 这会破坏装饰器的目的。它不应该阻塞。
    • @bstempi to intercept it before notifying the caller 对我来说意味着这个......虽然可能是我的英语......然后 codethulhu 的解决方案是要走的路:创建包装类,并实现 get() 以便它做肮脏的工作......
    • 在通知调用者之前拦截意味着装饰器应该知道Future在调用装饰器的对象之前返回。
    • 感谢您的示例!我遇到的问题是 //do the dirty stuff 需要在装饰器中发生,因为装饰器将是“做脏东西”的人,主要是因为“脏东西”涉及装饰器的私人成员。
    • @bstempi 好吧,如果 MyWrapper 被定义为非静态类 inside 装饰器类...
    【解决方案4】:

    您将要使用从装饰器返回的代理 Future。然后,您需要实现代理 Future 的 #get 方法来阻止,直到真正的 Future 返回,并且您已经能够对结果执行您的工作。

    class ProxyFuture<T> implements Future<T> {
    
        private Future<T> original;
        private Task task;
        ExecutorService executor;
    
        public ProxyFuture(Future<T> original, Runnable task, ExecutorService executor) {
            this.original = original;
            this.task = task;
            this.executor = executor;
        }
    
        public T get() throws InterruptedException, ExecutionException {
            T result = original.get();
            executor.execute(task);
            return result;
        }
    
    
    }
    

    在这里,您有一些用于 ExecutorService 的应用程序资源,并让您的 Decorator 创建一个 Runnable 并将其传递给新的 Future。这样,您的 ProxyFuture 将在将结果传递回原始请求者之前调用装饰器中的代码。

    以下是装饰器示例

    public class Decorator {
    
        Object original;
    
        public Decorator(Object original){
            this.original = original;
        }
    
        public void doDecoratedBehavior(){
    
        }
    
        public Runnable createTask(){
            return new Runnable(){
                public void run(){
                    doDecoratedBehavior();
                }
            };
        }
    }
    

    【讨论】:

    • 有没有一种简单的方法可以在不编写自定义 Future 类的情况下实现这一点?我想到了这个想法,但 Future 类的写作是负面的。
    • 您为什么不想编写一个自定义的 Future 类?这很简单。
    • 你是对的——这很简单。我认为Runnable task 是这个代理让装饰器知道它已经完成的一种方式?
    • @bstempi 添加了装饰器示例。
    【解决方案5】:

    根据您的描述,您的异步是毫无意义的。这是我对您所要求的内容的伪代码解释 -

    Future<Integer> getCalculation(){
        Future<Integer> future = realObject.getCalculation();
        //asynch code runs while you presumably do other stuff.
        ....
        ....
        result = future.get();
        return future;
    }
    

    但在这一点上,使用未来是没有意义的。异步为您节省了no 时间。事实上,由于额外的开销,它实际上使事情花费了更长。这是因为当您执行result = future.get() 时,您会等待线程完成,从而丢失您节省的所有时间。这也意味着返回未来没有任何优势,因为它除了result之外什么也做不了。

    换句话说,这段代码会更快,更省事(假设你在计算过程中不需要做任何事情)-

    Integer getCalculation(){
        return realObject.getCalculation();//assume it is rewritten to just return an int.
    }
    

    【讨论】:

    • 对此有两种回应。首先,我无法更改父方法的返回类型,所以我坚持使用Future。其次,我不希望getCalculation() 阻止。我想要第一个 dibs,但我不想等待它。
    • @bstempi 无法更改返回类型很糟糕,但那里无能为力。但是,如果您在 getCalculation() 期间没有做任何事情,那么您无论如何都必须等待。我写的第一个块会让你得到第一个 dib,但你基本上还是被阻止了,如果你坚持从你描述的内容中得到第一个 dib,你就无法绕过它。
    • 我想我正在寻找一种简单的方法来让计算异步运行,然后被回调,这样我就可以获得结果并修改返回到的 Future客户。我需要第一个 dib,但我不能阻止。
    猜你喜欢
    • 1970-01-01
    • 2016-06-12
    • 2015-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-21
    • 2021-08-14
    • 1970-01-01
    相关资源
    最近更新 更多