1.概述
在本文中,我们将学习如何在特定时间后结束长时间运行的执行。我们将探讨该问题的各种解决方案。另外,我们将介绍他们的一些陷阱。
2.使用循环
想象一下,我们正在循环处理一堆项目,例如电子商务应用程序中产品项目的一些详细信息,但是可能没有必要完成所有项目。
实际上,我们只想处理特定时间,然后,我们要停止执行,并显示该时间之前列表已处理的所有内容。
让我们看一个简单的例子:
long start = System.currentTimeMillis();
long end = start + 30*1000;
while (System.currentTimeMillis() < end) {
// Some expensive operation on the item.
}
在这里,如果时间超过了30秒的限制,则循环将中断。上述解决方案中有一些值得注意的要点:
- 准确性低:该循环的运行时间可能超过所施加的时间限制。这将取决于每次迭代可能花费的时间。例如,如果每次迭代最多可能需要7秒,那么总时间可能最多为35秒,这比所需的30秒的时限长了大约17%。
- 阻塞:主线程中的这种处理可能不是一个好主意,因为它将长时间阻塞它。相反,这些操作应与主线程解耦
在下一节中,我们将讨论基于中断的方法如何消除这些限制。
3.使用中断机制
在这里,我们将使用一个单独的线程来执行长时间运行的操作。主线程将在超时时向工作线程发送一个中断信号。
如果辅助线程仍处于活动状态,它将捕获该信号并停止其执行。如果工作线程在超时之前完成,则不会对工作线程产生任何影响。
让我们看一下worker线程:
class LongRunningTask implements Runnable {
@Override
public void run() {
try {
while (!Thread.interrupted()) {
Thread.sleep(500);
}
} catch (InterruptedException e) {
// log error
}
}
}
在这里, Thread.sleep
模拟了长时间运行的操作。取而代之的是,可以进行任何其他操作。检查中断标志很重要,因为并非所有操作都是可中断的。因此,在这种情况下,我们应该手动检查该标志。
另外,我们应该在每次迭代中检查该标志,以确保线程最多在一次迭代的延迟内停止执行自身。
接下来,我们将介绍三种发送中断信号的机制。
3.1 使用Timer
另外,我们可以创建一个TimerTask
来在超时时中断工作线程:
class TimeOutTask extends TimerTask {
private Thread t;
private Timer timer;
TimeOutTask(Thread t, Timer timer){
this.t = t;
this.timer = timer;
}
public void run() {
if (t != null && t.isAlive()) {
t.interrupt();
timer.cancel();
}
}
}
在这里,我们定义了一个TimerTask
,在创建它时会使用一个工作线程。一旦调用其run
方法,它将中断工作线程。在指定的延迟后, Timer
将触发TimerTask
Thread t = new Thread(new LongRunningTask());
Timer timer = new Timer();
timer.schedule(new TimeOutTask(t, timer), 30*1000);
t.start();
3.2 使用方法Future#get
我们也可以使用Future
get
方法来代替Timer
:
ExecutorService executor = Executors.newSingleThreadExecutor();
Future future = executor.submit(new LongRunningTask());
try {
f.get(30, TimeUnit.SECONDS);
} catch (TimeoutException e) {
f.cancel(true);
} finally {
service.shutdownNow();
}
在这里,我们使用ExecutorService
Future
实例的工作线程,该线程的get
方法将阻塞主线程直到指定的时间。在指定的超时后,它将引发TimeoutException
在catch
块,我们可以通过调用中断工作者线程cancel
了对方法F
uture
对象。
与前一种方法相比,此方法的主要好处是它使用池来管理线程,而Timer
仅使用单个线程(无池) 。
3.3 使用ScheduledExcecutorSercvice
我们还可以使用ScheduledExecutorService
中断任务。 ExecutorService
的扩展,并提供了与其他功能相同的功能,其中包括一些用于处理执行计划的方法。这可以在设置的时间单位延迟一定时间后执行给定任务:
ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
Future future = executor.submit(new LongRunningTask());
executor.schedule(new Runnable(){
public void run(){
future.cancel(true);
}
}, 1000, TimeUnit.MILLISECONDS);
executor.shutdown();
newScheduledThreadPool
方法创建了一个大小为2的预定线程池。 ScheduledExecutorService#
schedule
方法采用Runnable
,延迟值和延迟单位。
上面的程序将任务计划为从提交之日起一秒钟后执行。该任务将取消原始的长期运行任务。
请注意,与以前的方法不同,我们不会通过调用Future#get
方法来阻塞主线程。因此,它是所有上述方法中最优选的方法。
4.可以保证经过一定时间后停止执行吗?
无法保证一定时间后停止执行。主要原因是并非所有的阻塞方法都是可中断的。实际上,只有少数定义明确的方法可以中断。因此,如果线程被中断并设置了标志,则在到达这些可中断方法之一之前,不会发生任何其他事情。
例如, read
和write
方法只是,如果他们在调用与创建的流中断InterruptibleChannel
。 BufferedReader
不是可中断的流 。因此,如果线程使用它读取文件,则read
方法中阻塞的该线程上interrupt()
是没有用的。
但是,我们可以在每次循环读取后显式检查中断标志。这样可以保证一定的延迟来停止线程。但是,这不能保证在严格的时间后停止线程,因为我们不知道读取操作可能花费多少时间。
另一方面, Object
wait
方法是可中断的。因此,在设置了中断标志后wait
方法中阻塞的线程将立即引发InterruptedException
我们可以通过在方法签名中throws
InterruptedException
来识别阻塞方法。
一个重要的建议是避免使用不推荐使用的Thread.stop()
方法。停止线程会使它解锁它已锁定的所有监视器。发生这种情况是由于ThreadDeath
异常在堆栈上传播。
如果先前由这些监视器保护的任何对象处于不一致状态,则不一致的对象将对其他线程可见。这会导致很难发现和推理的任意行为。
5.结论
在本教程中,我们学习了在给定时间后停止执行的各种技术,以及每种技术的优缺点。
0 评论