这可能是一个有许多可能答案的问题,但我要求的是最好的设计,而不是"如何才能完成这些#34;
假设我们正在实现一个带有计算Pi的UI的程序。我可以打一个"开始"按钮开始计算和"停止"按钮中止计算,给我一个消息框,目前为止计算的Pi精度值最高。
我想直接的方法是在新线程中启动Runnable。 runnable计算Pi,并将当前值存储在共享变量中,两个线程都可以访问。 "停止"将中止线程,并显示共享变量。
我觉得这可以更优雅地实施,但我不确定如何。也许使用CompletableFuture?
我宁愿在没有向我的项目中添加任何新库的情况下解决这个问题,但是如果你知道一个特别支持这个库的库,请将它留在评论中。
显然,计算Pi永远不会完成。如果解决方案也支持例如,那将是很棒的。计算国际象棋中的最佳动作。如果有足够的时间,这将完成,但通常必须中止,返回到目前为止最好的举动。
答案 0 :(得分:2)
参考你计算Pi或计算国际象棋中最佳动作的例子,你的近似算法本质上是迭代的。就像国际象棋的Pi和MCMC的随机抽样一样。这让我想到两个appraoches。
Cou可以使用AtomicBoolean
这是一个线程安全的布尔变量。您需要将它传递给Runnable并在计算近似值时检查其状态。同时你停止计算的按钮监听器能够设置变量。
算法的迭代特性使得分割计算成为可能,并在以后再次聚合。例如,您计算1000次迭代,您可以将其拆分为200次迭代的块,计算这5个块并聚合结果。
我现在建议使用ExecutorCompletionService
和TimerTask
。我们的想法是计算少量迭代,这些迭代只需要很短的时间,并使用Executor
以新的Runnable
排斥TimerTask
。让我们说计算5个runnables需要1秒钟,你的计时器任务会每5秒将5个Runnables放入Executor。当您点击停止按钮时,您将停止产生并等待待处理任务完成后收集结果并产生结果。
当然,在调用完成服务的关闭方法后,你还需要一个告诉TimerTask
停止的变量,但是这个变量不是线程安全的。这种方法的另一个好处是你的计算是并发的,你可以很容易地充分利用任何CPU,只是产生更多的Runnables。同时执行此操作可以让您在更短的时间内计算更多,并获得更好的近似值。
答案 1 :(得分:0)
您的问题是如何实现仍然可以提供结果的可停止任务。近似值是一个很好的例子,但对于解决方案可以忽略。
例如,FutureTask
不会起作用,因为这些合同是他们在完成后自行决定的,他们只能得到结果或被取消。
共享(例如volatile
)变量听起来合理但有其缺点。当在紧密循环中定期更新时,您可能会观察到比使用局部变量更差的性能,并且只有在对象是例如对象时才能读取共享对象的状态。不可改变的,或者可以保证读写的顺序正确。
您还可以构建具有结果传递BlockingQueue
的内容,其中一旦请求中断,计算线程就会将当前结果(甚至是结果的定期更新)放入其中。
但最好的解决方案可能是(共享)CompletableFuture
。排序单个结果项队列,但它具有更好的语义来报告异常。
示例:
CompletableFuture<Integer> sharedFuture = new CompletableFuture<>();
Thread computing = new Thread(() -> {
int value = 1;
try {
while (!Thread.currentThread().isInterrupted() &&
!sharedFuture.isDone()) { // check could be omitted
value = value * 32 + 7;
}
sharedFuture.complete(value);
} catch (Throwable t) {
sharedFuture.completeExceptionally(t);
}
});
computing.start();
try {
Thread.sleep((long) (5000 * Math.random()));
} catch (InterruptedException ignored) {
}
computing.interrupt();
System.out.println(sharedFuture.get());
执行该任务的方式并不重要。您可以使用Thread
而不是ExecutorService
以上,而是取消Future
而不是中断线程。