Guava---AbstractFuture

9783 ワード

AbstractFutureの紹介を始める前に、手動でコールバックを実現しましょう.

コールバック


手書きFutureクラス
public class Future {
    private Consumer consumer;

    public void addListener(Consumer consumer) {
        this.consumer = consumer;
    }

    public void set(T value) {
        consumer.accept(value);
    }
}

テストコールバック
public static void main(String[] args) throws InterruptedException {
        Future future = new Future();

        // future 
        future.addListener(new Consumer() {
            @Override
            public void accept(String s) {
                System.out.println("---------"+s);
            }
        });

        TimeUnit.SECONDS.sleep(5);
        future.set("hh");

        TimeUnit.SECONDS.sleep(1);
    }

mianメソッドを実行すると、睡眠5秒後にfutureに値が設定されていることがわかる、addListenerメソッドはfutureに値が設定されていることを傍受することができる.以上のコールバックで問題があり、Consumerインタフェースのメソッドの実行に多くの時間がかかると仮定すると、future.set(「hh」)ではブロックが発生しますが、実際にはスレッドを有効にすることができますが、AbstractFutureはすでに私たちのために良い解決方法です.

AbstractFuture使用


まず一例を通してAbstractFutureのメリットを感じてみましょう
 static class AbstractFutureImpl extends AbstractFuture {
        public boolean set(T value) {
            return super.set(value);
        }

    }

    //  
    final static ExecutorService executors = Executors.newCachedThreadPool();

    public static void main(String[] args) throws InterruptedException {
        AbstractFutureImpl future = new AbstractFutureImpl();
        // future 
        future.addListener(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(" ");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(" set : " + future.get());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, executors);

        TimeUnit.SECONDS.sleep(5);
        future.set("hh");
        System.out.println("set ");
        TimeUnit.SECONDS.sleep(100);
    }
    /**
     set 
      
      set : hh
     */

印刷結果から分かるように、future.set(「hh」)メソッドはブロックされず、コールバック関数は完全に入力スレッドプールによって実行される.setが完了するとaddListenerメソッドがアクティブにコールバックされ、futureを通過する.get()は(このときすでに値があるのでget()メソッドがブロックすることはない)値を取得する.

AbstractFuture解析


AbstractFutureはListenableFuture、Futureインタフェースを実現し、インタフェースのすべての方法を実現した.車輪を繰り返す必要はありませんaddListenerはどのように実現したのでしょうか?具体的な実装の詳細はソースコードを見て、ここで大まかな考え方を述べる.
addListenerコアソース:
public void add(Runnable runnable, Executor executor) {
    // Fail fast on a null.  We throw NPE here because the contract of
    // Executor states that it throws NPE on null listener, so we propagate
    // that contract up into the add method as well.
    Preconditions.checkNotNull(runnable, "Runnable was null.");
    Preconditions.checkNotNull(executor, "Executor was null.");

    // Lock while we check state.  We must maintain the lock while adding the
    // new pair so that another thread can't run the list out from under us.
    // We only add to the list if we have not yet started execution.
    synchronized (this) {
      if (!executed) {
        runnables = new RunnableExecutorPair(runnable, executor, runnables);
        return;
      }
    }
    // Execute the runnable immediately. Because of scheduling this may end up
    // getting called before some of the previously added runnables, but we're
    // OK with that.  If we want to change the contract to guarantee ordering
    // among runnables we'd have to modify the logic here to allow it.
    executeListener(runnable, executor);
  }

ソースコードにはexecutedの判断があり、if()の判断はrunnableとexecuterを格納し、そうでなければexecuterでrunnableメソッドを直接実行する.なぜexecutedがあるのでしょうか?set(T value)メソッドのソースコードを見ると、:1.addListenerメソッドがsetメソッドよりも先に実行する場合、executedはfalseであり、setメソッドを実行する際にexecuterによってrunnableメソッドがアクティブに実行される.2.addListenerメソッドがsetメソッドよりも実行する場合、executedはtureである.直接executorでrunnableメソッドを実行する.
setコアソース:
public void execute() {
    // Lock while we update our state so the add method above will finish adding
    // any listeners before we start to run them.
    RunnableExecutorPair list;
    synchronized (this) {
      if (executed) {
        return;
      }
      executed = true;
      list = runnables;
      runnables = null;  // allow GC to free listeners even if this stays around for a while.
    }
    // If we succeeded then list holds all the runnables we to execute.  The pairs in the stack are
    // in the opposite order from how they were added so we need to reverse the list to fulfill our
    // contract.
    // This is somewhat annoying, but turns out to be very fast in practice.  Alternatively, we 
    // could drop the contract on the method that enforces this queue like behavior since depending
    // on it is likely to be a bug anyway.

    // N.B. All writes to the list and the next pointers must have happened before the above 
    // synchronized block, so we can iterate the list without the lock held here.
    RunnableExecutorPair reversedList = null;
    while (list != null) {
      RunnableExecutorPair tmp = list;
      list = list.next;
      tmp.next = reversedList;
      reversedList = tmp;
    }
    while (reversedList != null) {
      executeListener(reversedList.runnable, reversedList.executor);
      reversedList = reversedList.next;
    }
  }

思想は魂であり、実現は形式である.