概覽
Guava的介面代表了一個有執行狀態的物件,有啟動和停止的方法。比如網路伺服器,RPC伺服器,以及計時器等,都可以實現Service介面。掌管像這樣的服務的狀態,需要正確地管理啟動和關閉,因此會是繁瑣的,特別是牽扯到多執行緒和排程。Guava提供了一個基本的骨架,可以幫你管理狀態邏輯,以及同步的細節
一個Service的通常的生命週期是
Service.State.NEW
到Service.State.STARTING
到Service.State.RUNNING
到Service.State.STOPPING
到Service.State.TERMINATED
一個已經停止的Service是不能被重啟的。如果這個服務在啟動或者執行、停止時失敗,那麼它會轉入Service.State.FAILED
狀態。
一個服務可以被使用 startAsync()
來非同步地啟動,這個方法會返回'this'引用,以方便地使用方法鏈。只有這個服務是NEW
狀態的時候才能呼叫。所以,你的程式應該有個唯一的地方來啟動每一個服務。
停止服務是類似的,使用非同步的stopAsync()
方法。但是不像startAsync()
方法,它可以安全地呼叫多次。這使得可以它可以應對在關閉伺服器可能遇到的競爭問題。
服務也提供了幾種方法來等待服務狀態轉換的完成。
- 非同步地使用
addListener()
。addListener()允許你新增一個Service.Listener
,它會在服務的服務狀態轉換時被呼叫。需要注意的是,如果在這個listener被新增時,伺服器已經不是NEW
的狀態。,那麼已經發生過的狀態轉換是不會在這個listener上重放。 - 非同步地使用
awaitRunning()
。它是不可中斷的(uniterruptible),不會丟擲受檢異常,會在服務啟動完畢時返回。如果服務啟動失敗,會丟擲一個IllegalStateException。類似的是awaitTerminated()
,呼叫它等待服務到達終止狀態(TERMINATED
orFAILED
)。這兩個方法都有過載的方法,允許指定超時時間。
Service
介面是微妙的。我們不建議直接實現它。你可以使用我們在guava提供幾個虛類中的一個作為你的實現的基礎。每個基類提供了一個特定的執行緒模型。
實現
AbstractIdleService
AbstractIdleService
骨架實現了一個Service,這個服務不需要在“running”狀態執行任何動作,因此它在執行時也不需要一個執行緒,但是它仍然需要執行啟動和停止的動作。實現這樣的服務只需要繼承AbstractIdleService,並且實現 startUp()
以及shutDown()
方法,
protected void startUp() { servlets.add(new GcStatsServlet()); } protected void shutDown() {}
需要指出的是,任何對GcStatsServlet的查詢已經執行於一個執行緒中了。因此,在這個服務執行時,我們不需要它執行任務操作。
AbstractExecutionThreadService
一個AbstractExecutionThreadService
在同一個執行緒中執行啟動,動行和關閉操作。你必須重寫run()
方法,而且這個方法必須對關閉請求作為響應。比如,你或許會在一個工作迴圈中執行這些動作:
public void run() { while (isRunning()) { // perform a unit of work } }
或者,你可以覆蓋 triggerShutdown()
方法,只要它能夠使得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()方法,並且等待執行緒停止。(譯註:重點是run()方法不是在呼叫start()的執行緒中執行的)
AbstractScheduledService
一個AbstractScheduledService
在它動行時執行一些週期性的任務。它的子類覆蓋runOneIteration()
來指定每個週期的任務,同時也需要覆蓋我們熟悉的startUp和shutDown()
方法
為了描述怎麼排程執行,你需要實現scheduler()
方法。通常而言,你會使用已經AbstractScheduledService.Scheduler
提供的幾個排程器中的一個,newFixedRateSchedule(initialDelay, delay, TimeUnit)
或者newFixedDelaySchedule(initialDelay, delay, TimeUnit)
,與我們熟知的ScheduledExecutorService中的方法相類。自定義的排程器可以用來實現,請參見Javadoc.
AbstractService
如果你需要自己管理執行緒,就需要直接覆蓋AbstractService
。通常來說,以上提到的幾種實現就夠用了,但是,有些情況下是建議實現AbstractService的,比如,你對有自己的執行緒語義的東西進行建模,把它做成一個Service,你就會有自己特殊的執行緒相關的需求。
為了實現AbstractService,你需要實現這兩個方法
doStart()
:doStart()在第一次呼叫startAsync()時被直接呼叫,你的doStart()方法應該完成所有初始化工作,並且在啟動成功時呼叫,或者啟動失敗時呼叫。doStop()
:doStop()在第一次呼叫stopAsync()時被呼叫,你的doStop()方法應該關閉你的服務,並且如果關閉成功,最終呼叫notifyStopped()
,或者關閉失敗,就呼叫notifyFailed()
。
你的doStart和doStop方法應該執行得很快。如果你需要進行昂貴的初始化工作,比如讀取檔案,開啟網路連線,或者進行任何可能會阻塞的工作,你需要移動這些工作到另一個執行緒去做。
使用SerivceManager
在Service的骨架實現之外,Guava還提供了一個ServiceManager
類,它使得一些跟多個Service的實現相關操作操作更簡單一些。使用Service的集合建立一個新的ServiceManager。然後你就可以管理它們了:
startAsync()
將會啟動所有被管理的服務。就像一樣Service#startAsync()
,你只可以在所有的服務都是NEW
的時候呼叫這個方法一次stopAsync()
將會停止所有被管理的服務addListener
將會新增一個ServiceManager.Listener
,它會在所有主要的狀態轉換時被呼叫awaitHealthy()
將會等待所有的服務達到RUNNING狀態awaitStopped()
將會等待所有的服務到達終止狀態。
或者檢查這些服務:
isHealthy()
在所有服務都在狀態時返回true- 將會返回一個所有服務的一致的snapshot,以它們的狀態作為索引。
startupTimes()
將會返回從受管理的Service到它花了多少毫秒啟動的map.Guava保證這個返回的map將按照啟動時間(startup time)排序。
別然我們建議服務的生命週期通過ServiceManager來管理,但是通過其它機制進行的狀態轉換不會影響它的方法的正確性。比如,如果一些服務不是通過startAsync()啟動的,監聽器仍然會在合適的時機被呼叫,並且 awaitHealthy()
也會照常工作。ServiceManager的唯一要求是在ServiceManager構造時,所有的Serivce都在NEW狀態。