我有一个需要执行的任务队列,以及一个接收任务并执行它们的工作池。还有一个“经理”课程,可以跟踪工作人员,允许用户停止或重新启动工作人员,报告他们的进度等。每个工作人员都做这样的事情:
public void doWork() {
checkArguments();
performCalculation();
saveResultsToDatabase();
performAnotherCalculation();
saveResultsToDatabase();
performYetAnotherCalculation();
saveResultsToDatabase();
}
在这种情况下,“database”不一定是指Oracle数据库。这肯定是其中一个选项,但结果也可以保存在磁盘上,Amazon SimpleDB等中。
到目前为止,这么好。但是,由于各种因素,有时performCalculation()代码会间歇性地锁定,但主要是由于一堆第三方库中的网络代码实现不佳(f.ex.Socket.read()永远不会返回) 。显然这很糟糕,因为任务现在永远停滞不前,工人现在已经死了。
我想要做的是将整个doWork()方法包装在某种超时中,如果超时到期,则将任务交给其他人。
但我怎么能这样做呢?假设原始工作者陷入“performCalculation()”方法。然后我将任务交给其他完成它的工作人员,然后原始工作人员决定唤醒并将其中间结果保存到数据库中......从而破坏完全有效的数据。我可以使用一些通用模式来避免这种情况吗?
我可以看到几个解决方案,但是大多数解决方案需要从头开始对所有业务逻辑代码进行严格的重构......这可能是哲学上正确的做法,但根本不是我有时间。
答案 0 :(得分:1)
您是否尝试使用Future
?它们对于运行任务并等待它完成,使用超时等非常有用。例如:
private Runnable performCalc = new Runnable() {
public void run() {
performCalculation();
}
}
public void doWork() {
try {
ExecutorService executor = Executors.newFixedThreadPool(1);
executor.submit(performCalc).get(); // Timeouts can be used here.
executor.submit(anotherCalc).get();
} catch(InterruptedException e) {
// Asked to stop. Rollback out transactions.
} catch(OtherExceptions here) {
}
}
答案 1 :(得分:1)
如果performCalculation
卡在阻止IO上,那么你几乎无法打断它。一种解决方案是使用Socket.setSoTimeout
关闭底层套接字或设置套接字操作的超时,但您必须拥有从套接字读取的代码才能执行此操作。
否则,您可以在将数据保存到数据库之前添加一些协调机制。使用某种时间戳来检测数据库中的数据是否比原始工作者从网络中获取的数据更新。
答案 2 :(得分:0)
我认为最简单的方法是使用一个单独的计时器线程,在带有performCalculation()的线程启动时启动。计时器线程可以在一段时间后唤醒并Thread.interrupt()
计算线程,然后在处理InterruptedException时执行任何必要的回滚。
当然,这是为了解决管理其他问题的额外复杂性,因此不是最优雅的解决方案。