概览
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状态。