Google Guava與併發操作相關的類

併發:功能強大而簡單的抽象,讓編寫正確的併發代碼更加容易。

ListenableFuture:完成後回調的Future
Service:啓動和關閉的服務,爲你處理複雜的狀態邏輯。

1.ListenableFuture

併發是一個困難的問題,但是通過使用功能強大且簡單的抽象可以大大簡化併發。爲了簡化問題,Guava使用ListenableFuture擴展了JDK的Future接口。

我們強烈建議你在所有代碼中始終使用ListenableFuture而不是Future,因爲:

  • 大多數Futures方法都需要它。
  • 比以後更改爲ListenableFuture更容易。
  • 工具方法的提供者無需提供其方法的FutureListenableFuture變體。

1.1接口

傳統的Future表示異步計算的結果:可能已經或可能尚未完成產生結果的計算。Future可以作爲正在進行的計算的句柄,是服務向我們提供結果的承諾。

ListenableFuture允許你在計算完成後或在計算已經完成時立即註冊要執行的回調。這個簡單的附加功能使它可以有效地支持基本Future接口無法支持的許多操作。

ListenableFuture添加的基本操作是addListener(Runnable, Executor),它指定當此Future表示的計算完成時,指定的Runnable將在指定的Executor上運行。

1.2添加回調

大多數用戶更喜歡使用Futures.addCallback(ListenableFuture, FutureCallback, Executor)FutureCallback實現兩種方法:

1.3創建

對應於JDK的ExecutorService.submit(Callable)方法來啓動異步計算,Guava提供了ListeningExecutorService接口,該接口在ExecutorService返回正常Future的任何地方都返回ListenableFuture。要將ExecutorService轉換爲ListeningExecutorService,只需使用MoreExecutors.listeningDecorator(ExecutorService)

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
ListenableFuture<Explosion> explosion = service.submit(
    new Callable<Explosion>() {
      public Explosion call() {
        return pushBigRedButton();
      }
    });
Futures.addCallback(
    explosion,
    new FutureCallback<Explosion>() {
      // we want this handler to run immediately after we push the big red button!
      public void onSuccess(Explosion explosion) {
        walkAwayFrom(explosion);
      }
      public void onFailure(Throwable thrown) {
        battleArchNemesis(); // escaped the explosion!
      }
    },
    service);

另外,如果你要從基於FutureTask的API進行轉換,則Guava提供了ListenableFutureTask.create(Callable)ListenableFutureTask.create(Runnable, V)。與JDK不同,ListenableFutureTask不能直接擴展。

如果你更喜歡抽象的方式設置future值,而不是實現一種計算該值的方法,請考慮擴展AbstractFuture或直接使用SettableFuture

如果必須將另一個API提供的Future轉換爲ListenableFuture,則別無選擇,只能使用重量級的JdkFutureAdapters.listenInPoolThread(Future)Future轉換爲ListenableFuture。只要有可能,最好修改原始代碼以返回ListenableFuture

1.4應用

使用ListenableFuture的最重要原因是可以擁有複雜的異步操作鏈。

ListenableFuture<RowKey> rowKeyFuture = indexService.lookUp(query);
AsyncFunction<RowKey, QueryResult> queryFunction =
  new AsyncFunction<RowKey, QueryResult>() {
    public ListenableFuture<QueryResult> apply(RowKey rowKey) {
      return dataService.read(rowKey);
    }
  };
ListenableFuture<QueryResult> queryFuture =
    Futures.transformAsync(rowKeyFuture, queryFunction, queryExecutor);

ListenableFuture可以有效地支持許多其他操作,而單獨的Future不能支持。不同的執行者可以執行不同的操作,並且單個ListenableFuture可以有多個操作在等待它。

當多個操作應該在另一個操作啓動時立即開始時——“扇出”——ListenableFuture只起作用:它觸發所有請求的回調。稍微多做一些工作,我們可以“扇入”或觸發一個ListenableFuture,以便在其他幾個future都完成後立即進行計算:有關示例,請參見Futures.allAsList的實現

方法 描述 參見
transformAsync(ListenableFuture, AsyncFunction, Executor)* 返回一個新的ListenableFuture,其結果是將給定的AsyncFunction應用於給定ListenableFuture的結果的產物。 transformAsync(ListenableFuture, AsyncFunction)
transform(ListenableFuture, Function, Executor) 返回一個新的ListenableFuture,其結果是將給定的Function應用於給定ListenableFuture的結果的產物。 transform(ListenableFuture, Function)
allAsList(Iterable>) 返回一個ListenableFuture,其值是按順序包含每個輸入future的值的列表。如果任何一個輸入future失敗或被取消,則該future失敗或被取消。 allAsList(ListenableFuture...)
successfulAsList(Iterable>) 返回一個ListenableFuture,其值是按順序包含每個成功輸入future的值的列表。與失敗或取消的future相對應的值將替換爲null successfulAsList(ListenableFuture...)

* AsyncFunction<A, B>提供一個方法ListenableFuture<B> apply(A input)。它可以用於異步轉換值。

List<ListenableFuture<QueryResult>> queries;
// The queries go to all different data centers, but we want to wait until they're all done or failed.

ListenableFuture<List<QueryResult>> successfulQueries = Futures.successfulAsList(queries);

Futures.addCallback(successfulQueries, callbackOnSuccessfulQueries);

1.5避免嵌套Future

在代碼調用通用接口並返回Future的情況下,可能會以嵌套的Futures結尾。例如:

executorService.submit(new Callable<ListenableFuture<Foo>() {
  @Override
  public ListenableFuture<Foo> call() {
    return otherExecutorService.submit(otherCallable);
  }
});

將返回一個ListenableFuture<ListenableFuture<Foo>>。這段代碼是不正確的,因爲如果外部future的取消與外部future的完成進行競爭,則該取消將不會傳播到內部future。使用get()或監聽器檢查另一個future是否失敗也是常見的錯誤,但是除非特別小心,否則從otherCallable拋出的異常將被抑制。爲了避免這種情況,Guava的所有future處理方法(以及JDK中的某些方法)都具有*Async版本,可以安全地解開此嵌套——transform(ListenableFuture, Function, Executor)transformAsync(ListenableFuture, AsyncFunction, Executor)ExecutorService.submit(Callable)submitAsync(AsyncCallable, Executor)等等。

2.Service

Guava Service接口表示一個具有操作狀態的對象,並帶有啓動和停止的方法。例如,Web服務器,RPC服務器和計時器可以實現Service接口。管理這些服務的狀態(需要適當的啓動和關閉管理)並非易事,特別是在涉及多線程或日程調度schedule的情況下。Guava提供了一些框架來爲你管理狀態邏輯和同步細節。

2.1使用Service

服務Service的正常生命週期是

已停止的服務無法重新啓動。如果服務在啓動、運行或停止的地方失敗,它將進入Service.State.FAILED狀態。

如果服務是NEW,則可以使用startAsync()異步啓動服務。因此,你應該將應用程序結構化爲在每個服務啓動時都有唯一的位置(統一)。

使用異步stopAsync()方法來停止服務也是類似的。但是與startAsync()不同,多次調用此方法是安全的。這使得處理關閉服務時可能發生的競爭成爲可能。

服務還提供了幾種方法來等待服務轉換完成。

  • 異步使用addListener()addListener()允許你添加一個Service.Listener,它將在服務的每個狀態轉換時調用。注意:如果在添加監聽器時服務不是NEW新建的,那麼任何已經發生的狀態轉換都不會在監聽器上重新觸發。
  • 同步使用awaitRunning()。這是不中斷的,不會拋出已檢查的異常,並在服務啓動完成後返回。如果服務啓動失敗,則會拋出IllegalStateException。同樣,awaitTerminated()等待服務達到終端狀態(TERMINATEDFAILED)。兩種方法都具有重載的允許指定超時時間。

Service接口是微妙而複雜的。我們不建議直接實現它。相反,請使用guava中的抽象基類之一作爲實現的基礎。每個基類都支持特定的線程模型。

2.2實現

2.2.1AbstractIdleService

AbstractIdleService框架實現了Service,該服務在處於“運行”狀態時不需要執行任何操作——因此在運行時不需要線程——但具有要執行的啓動和關閉操作。實現這樣的服務與擴展AbstractIdleService以及實現startUp()shutDown()方法一樣容易。

protected void startUp() {
  servlets.add(new GcStatsServlet());
}
protected void shutDown() {}

請注意,對GcStatsServlet的任何查詢都已經有一個在運行的線程。在服務運行時,我們不需要該服務自行執行任何操作。

2.2.2AbstractExecutionThreadService

AbstractExecutionThreadService在單個線程中執行啓動、運行和關閉操作。你必須重寫run()方法,並且它必須響應停止請求。例如,你可以在工作循環中執行操作:

public void run() {
  while (isRunning()) {
    // perform a unit of work
  }
}

或者,你可以以任何方式重寫,從而使run()返回。

重寫startUp()shutDown()是可選的,但是將爲你管理服務狀態。

protected void startUp() {
  dispatcher.listenForConnections(port, queue);
}
protected void run() {
  Connection connection;
  while ((connection = queue.take() != POISON)) {
    process(connection);
  }
}
protected void triggerShutdown() {
  dispatcher.stopListeningForConnections(queue);
  queue.put(POISON);
}

請注意,start()調用你的startUp()方法,爲你創建一個線程,並在該線程中調用run()stop()調用triggerShutdown()方法並等待線程死亡。

2.2.3AbstractScheduledService

AbstractScheduledService在運行時執行一些週期性任務。子類實現runOneIteration()來指定任務的一次迭代,以及熟悉的startUp()shutDown()方法。

要描述執行日程調度schedule,你必須實現scheduler()方法。通常,你將使用AbstractScheduledService.Scheduler提供的日程schedule之一,newFixedRateSchedule(initialDelay, delay, TimeUnit)newFixedDelaySchedule(initialDelay, delay, TimeUnit),與ScheduledExecutorService中熟悉的方法相對應。可以使用CustomScheduler來實現自定義日程調度schedule;有關詳細信息,請參見Javadoc。

2.2.4AbstractService

當你需要執行自己的手動線程管理時,請直接重寫AbstractService。通常,上述實現之一應該可以爲你提供良好的服務,但是當你在建模某種提供自己的線程語義作爲Service時,建議你實現AbstractService,因爲你有自己特定的線程需求。

要實現AbstractService,必須實現2個方法。

  • doStart()doStart()是第一次調用startAsync()直接調用的,你的doStart()方法應執行所有的初始化,如果啓動成功,則最終調用notifyStarted(),如果啓動失敗,則最終調用notifyFailed()
  • doStop()doStop()是由第一次調用stopAsync()直接調用的,你的doStop()方法應關閉服務,如果關閉成功,則最終調用notifyStopped(),如果關閉失敗,則最終調用notifyFailed()

你的doStartdoStop方法應該是快速的。如果你需要進行昂貴的初始化,例如讀取文件、打開網絡連接或任何可能阻塞的操作,則應考慮將該工作移至另一個線程。

2.3使用ServiceManager

除了Service框架實現之外,Guava還提供了ServiceManager類,它使涉及多個服務實現的某些操作更加容易。使用Services集合創建一個新的ServiceManager。然後,你可以管理它們:

或檢查它們:

  • 如果所有服務都是RUNNING,則isHealthy()返回true。
  • servicesByState()返回按狀態索引的所有服務的一致快照。
  • startupTimes()返回管理下的Service到該服務啓動所需的時間(以毫秒爲單位)的映射。返回的映射保證按啓動時間排序。

雖然建議通過ServiceManager管理服務生命週期,但是通過其他機制啓動的狀態轉換不會影響其方法的正確性。例如,如果服務是由startAsync()之外的某種機制啓動的,則監聽器將在適當的時候被調用,而awaitHealthy()仍將按預期工作。ServiceManager強制執行的唯一要求是,在構造ServiceManager時,所有Service都必須是NEW

本文參考:
ListenableFutureExplained
ServiceExplained
guava-tests-concurrent

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章