一起学习
很多时候,在应用程序运行期间你需要一个类来执行一些像数据处理、侦听事件或者检查另一个类活动情况的任务。你可能要通过一组锁定和告知来使用线程从而实现这一点。虽然Java线程API具有丰富的文档说明,但是还是需要大量的代码和经验才能让你的线程正确地和有效地工作。你可以使用暂存来避免每次需要的时候都必须编写这样的类,你也可以使用本文中将讨论的框架来创建一个更加稳健的应用程序。
--------------------------------------------------------------------------------
获得代码
--------------------------------------------------------------------------------
用于长期运行任务的框架
长期运行任务的主要特点是:在应用程序运行期间它必须要一直保持运行。要实现这一点的正确方法是为特定任务提供一个执行线程。你需要创建一个任务,将其作为一个线程或者作为java.lang.Runnable接口的一个实现。如果实现了Runnable,你就可以获得面向对象更好的设计,还可避免单方继承的问题。你也可以更加有效地操控Runnable实例,例如,使用一个线程池来运行,这个池通常需要一个Runnable实例,而不是一个线程。
框架的实质就是Worker抽象类(Listing A),它是用来实现Runnable接口,并为有效地处理任务提供Helper方法的。有些方法是完全实现的,比如run()方法,但是有的是抽象的,需要由你来填充。如果要创建一个长期运行的类,你只需要扩展Worker类,并实现几个抽象方法就行了。现在我们更加仔细地看看这些方法。
Worker类的run()方法是设计用来持续执行work()方法直到其停止的。work()能够负责数据处理、对某个事件做出反应、读取或者编写文件、SQL执行等等。它可以丟置例外,所以把它推广出去以及让run()方法来处理它都是好方法。
run()方法有两层try-catch子句:while循环外面和里面。第一个try-catch子句是用于捕捉所有非程序的例外,而且保证run()方法决不会退出。第二个子句会捕捉住任何属于事务逻辑的例外,并做出相应的行为。如果某个等待操作替代了work()方法(例如服务于InputStream或者Socket),那么最好传播InterruptedException。要记住的是,work()方法不需要任何while循环来维持其在应用程序运行期间的运行。Worker能为你做这件事。
run()方法开始的时候,它会调用prepareWorker(),后者是设计用来为长期运行任务(Listing A)准备所需资源的。在这个方法调用的过程中,你可以,例如建立一个数据库的连接,或者打开一个以后要被用到的文件。在这个地方放置一些封锁操作,例如打开一个socket,将会是非常好的,因为它们会在一个单独的线程里完成,这样就不会封锁主执行线程了。
前一个方法相反的方法是releaseWorker(),它在run()即将退出的时候(Listing A)被调用。这里,你可以使用代码来释放这个任务所使用的系统资源,或者进行其他的清除工作。这个方法和java.lang.Object.finalize()相类似,但是它是在线程中止之前被明确调用的。
处理框架的错误
另一个重要的方法是handleError(),它把java.lang.Throwable作为一个参数。这个方法在每次run()方法里出现错误情形时被调用。轮到你实现错误处理了。一种方法是通过调用halt()方法(Listing A)登记错误和控制任务的终止。
isCondition()方法是被用来告知work()方法的执行是否能被启动,这样就能允许对任务的精确控制了。在work()方法的执行悬而未决,直到出现某个条件为止——例如,缓存非空,的这段时间里,事件触发框架是很有用的。在Worker的实现里,条件会按照锁定告知的内容定期被检查,其时间间隔由你在setTimeout()方法(Listing A)里指定。如果不需要任务里的任何等待区段,那么你只用让isCondition()方法总是返回真值就行了。
什么时候中止
你还需要isRunning()、broadcast()和halt()方法。查询isRunning(),你可以检查一个任务是否还在运行,并决定是否中止它。broadcast()方法只是用来通知锁定对象并让任务继续运行的,如果它一直服务于这个锁定的话。halt()方法能够停止一个任务,所以run()方法会在下一个isRunning()的状态一被检查的时候就退出。由于这个方法只通知能够封锁这个任务线程的锁定,所以当你在work()方法里(Listing B)进行锁定操作的时候最好使用同一个锁定对象。如果不能使用同一个锁定对象,例如在你正在追踪java.io.InputStream.read()方法的时候,你就应该向halt()添加所有可能锁定的明确告知,或者向其添加java.lang.Thread.interrupt()。如果被追踪的对象能正确地处理这个信号,那么java.lang.Thread.interrupt()就能正常工作了。例如,它能对InputStream.read()起作用,但是对java.sql.PreparedStatement.execute()就没有用了,所以每个特定的情况下你都必须测试halt()方法。
一旦熟悉了如何使用Worker类,你就可以很容易地创建自己的实现(Listing B)。要把这个类作为一个线程来运行,只用简单地使用一个新的Thread(new WaitedWorker()).start就行了。使用Thread.interrupt()或者Worker.halt()或者两者联合使用,你就能精确地控制任务的执行了。例如,你可以通过把相应的代码放到java.lang.Runtime.addShutdownHook()方法里,这样在JVM停止的时候就能够停止所有的worker了。
结论
我们已经了解了长期运行任务的框架,也看过了如何在其抽象类的基础上创建一个新的任务。其构架是很清晰和灵活的,设计初就具有可扩展性。有了这个框架,你可以避免从暂存里创建类,也能够开发更加有效和可靠的应用程序了。
下载本文示例代码
如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务如何有效地处理Java里长期运行的任务
阅读(355) | 评论(0) | 转发(0) |