技术开发 频道

JCA 1.5: 工作管理和事务流入

        为什么等待?

  您已经看到 doWork 方法允许您在调用线程阻塞的同时执行工作。但是如果您不想等待 —— 也就是说,如果您不想同步执行工作,该怎么办呢?在 Java 2 平台标准版本(J2SE)环境中,可以使用多线程实现这一目标。但是,J2EE 规范让应用程序服务器使用 Java 2 安全管理器防止应用程序离开自己的线程。如果应用服务器的这部分功能是通过 EJB 池或 servlet 池来提供并发性,那么这样做是合理的。服务器也可能想把各种形式的上下文都关联到一个线程上,因为产生新线程时,上下文可能会丢失。最后,正如我以前讨论过的,不受服务器控制的线程会造成有序关机很难实现。

  虽然可以通过使用安全策略,逐个案例地克服这个限制,但是 WorkManager 上的 startWork 和 scheduleWork 方法可以让资源适配器异步地处理工作,同时确保应用程序服务器仍然在控制之下。startWork 方法会一直等候,直到工作片断开始执行,但不用等到工作结束。所以,如果调用器需要知道工作是否已经得以执行,但是不需要等到工作完成,那么可以使用这种方法。相比之下,只要该工作调度被接受,scheduleWork 方法就立即返回。在这种情况下,并不能确保工作真的被执行。

  清单 2 中的 WorkManager 方法的第 4 个参数(WorkListener)在用于这些异步方法时最有用。清单 5 显示了这个接口:

  清单 5. 接收事件通知的 WorkListener 接口

1 public interface WorkListener {
2     void workAccepted(WorkEvent e);
3     void workRejected(WorkEvent e);
4     void workStarted(WorkEvent e);
5     void workCompleted(WorkEvent e);
6     
7 }

  资源适配器能够有选择地传递一个监听器,当工作的项目通过接受、启动或完成这几个状态(失败的情况下则是拒绝)传递过来时,监听器会得到通知。在这个监听器已经完成其工作项目,或者出现故障要重新安排工作项目时,可以用它将通知发送给工作的发起者。WorkAdapter 类也包含在内,它提供了所有方法的默认实现,因此,子类只需覆盖自己感兴趣的方法即可。

  WorkListener 的每个方法都采用 WorkEvent 对象作为参数,如清单 6 所示:

  清单 6. WorkEvent 类上的附加方法

1 public class WorkEvent extends EventObject {
2     ...
3     public int getType() { ... }
4     public Work getWork() { ... }
5     public long getStartDuration() { ... }
6     public WorkException getException() { ... }
7
8 }

  除了通常的事件方法之外,WorkEvent 类还为这些类型的事件 (接受、拒绝、启动或完成)以及有问题的工作项目提供了存取器。这使您能够用一个(多线程的)监听器负责多个工作提交。还有一些方法可以返回启动工作所花费的时间,而且,在出现 workRejected 或 workCompleted 时,返回可能发生的对应的 WorkRejectedException 或 WorkCompletedException 异常。

  图 1 显示了通过 Work 对象传递的状态。

  图 1. Work 对象的状态

  沿着图 1 的底部,有三种提交方法,从上到下的点线表示具体的方法返回的生命周期中的时间点。

  可以推迟到明天的事为什么要现在做?

  doWork、startWork 和 scheduleWork 方法可以都立即向 WorkManager 提交任务。在 WorkManager 执行提交之前,可能会发生延迟,但是调用者只能控制最大延迟(用启动超时)。那么如果想让任务晚些而不是立即处理,该怎么办呢?scheduleWork 方法只允许将工作安排到另一个线程,而不是安排到时间轴上的以后某个时间点上。

  这正是 BootstrapContext 接口的第 3 个方法发挥其作用的地方。createTimer 方法使资源适配器能够得到 java.util.Timer 类的实例。这个类从 1.3 版开始就已经成为标准 Java 库的一部分,如清单 7 所示:

  清单 7. Timer 类的方法

1 public class Timer  {
2
3     public void schedule(TimerTask task, long delay) { ... }
4     public void schedule(TimerTask task, Date time) { ... }
5     public void schedule(TimerTask task, Date firstTime, long period) { ... }
6     public void schedule(TimerTask task, long delay, long period) { ... }
7     public void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) { ... }
8     public void scheduleAtFixedRate(TimerTask task, long delay, long period) { ... }
9     public void cancel() { ... }
10
11 }

  可以用 java.util.Timer 类的前两个方法把任务安排在指定延迟之后或指定日期和时间发生。余下的 4 个调度方法还有额外的 period 参数。可以用它们对那些初次运行之后需要按照常规间隔发生的事件进行调度,使用周期参数指定时间间隔。schedule 和 scheduleAtFixedRate 方法是不同的方法,因为这些操作的计时无法保证;像垃圾搜集这样的操作可能会造成任务推迟执行。如果任务被延迟,那么 schedule 方法在运行下一个任务之前仍然会等候一个完整的周期。但是,scheduleAtFixedRate 方法是在前一个任务 应当 运行的固定周期之后运行下一个任务。所以,如果任务之间的时间对您非常很重要,那么请使用 schedule。如果绝对时间或累积时间很重要,那么请使用 scheduleAtFixedRate。

  每个 schedule 方法都采用一个扩展了 TimerTask 类的对象作为自己的第一个参数。同使用 Work 接口时一样,这个类扩展了 Runnable ,而且 Timer 会在适当的时间调用 run 方法。TimerTask 类有一个 cancel 方法,可以用它取消对任务的后续调用。或者,也可以调用 Timer 上的 cancel 方法来取消目前安排的所有任务。scheduledExecutionTime 方法允许 run 方法将当前实际调用时间和它应当被调用的时间进行比较。

  虽然 TimerTask 的 run 方法是在新线程中调用 ,但是这个线程是 JVM 已经分配的线程,处于应用服务器的控制之外。如果您想让资源适配器进行严肃的处理,那么在这个时候,应当用 WorkManager 切换到应用服务器的线程。清单 8 显示的示例采用了这一良好实践来调度工作,从当前时间开始每分钟执行一次:

  清单 8. 组合使用 Timer 和 WorkManager

1 final WorkManager workManager = context.getWorkManager();
2 final Timer timer = context.createTimer();
3 timer.scheduleAtFixedRate(new TimerTask() {
4     public void run() {
5         workManager.scheduleWork(new ExampleWork());
6     }
7 }, 0, 60 * 1000);
0
相关文章