Guava---AbstractFuture
9783 ワード
AbstractFutureの紹介を始める前に、手動でコールバックを実現しましょう.
手書きFutureクラス
テストコールバック
mianメソッドを実行すると、睡眠5秒後にfutureに値が設定されていることがわかる、addListenerメソッドはfutureに値が設定されていることを傍受することができる.以上のコールバックで問題があり、Consumerインタフェースのメソッドの実行に多くの時間がかかると仮定すると、future.set(「hh」)ではブロックが発生しますが、実際にはスレッドを有効にすることができますが、AbstractFutureはすでに私たちのために良い解決方法です.
まず一例を通してAbstractFutureのメリットを感じてみましょう
印刷結果から分かるように、future.set(「hh」)メソッドはブロックされず、コールバック関数は完全に入力スレッドプールによって実行される.setが完了するとaddListenerメソッドがアクティブにコールバックされ、futureを通過する.get()は(このときすでに値があるのでget()メソッドがブロックすることはない)値を取得する.
AbstractFutureはListenableFuture、Futureインタフェースを実現し、インタフェースのすべての方法を実現した.車輪を繰り返す必要はありませんaddListenerはどのように実現したのでしょうか?具体的な実装の詳細はソースコードを見て、ここで大まかな考え方を述べる.
addListenerコアソース:
ソースコードには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コアソース:
思想は魂であり、実現は形式である.
コールバック
手書き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;
}
}
思想は魂であり、実現は形式である.