拨开荷叶行,寻梦已然成。仙女莲花里,翩翩白鹭情。
IMG-LOGO
主页 文章列表 如何在Java中经过一定时间后停止执行

如何在Java中经过一定时间后停止执行

白鹭 - 2021-11-24 549 0 0

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方法将阻塞主线程直到指定的时间。在指定的超时后,它将引发TimeoutExceptioncatch块,我们可以通过调用中断工作者线程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.可以保证经过一定时间后停止执行吗?

无法保证一定时间后停止执行。主要原因是并非所有的阻塞方法都是可中断的。实际上,只有少数定义明确的方法可以中断。因此,如果线程被中断并设置了标志,则在到达这些可中断方法之一之前,不会发生任何其他事情

例如, readwrite方法只是,如果他们在调用与创建的流中断InterruptibleChannelBufferedReader不是可中断的流 。因此,如果线程使用它读取文件,则read方法中阻塞的该线程上interrupt()是没有用的。

但是,我们可以在每次循环读取后显式检查中断标志。这样可以保证一定的延迟来停止线程。但是,这不能保证在严格的时间后停止线程,因为我们不知道读取操作可能花费多少时间。

另一方面, Object wait方法是可中断的。因此,在设置了中断标志后wait方法中阻塞的线程将立即引发InterruptedException

我们可以通过在方法签名中throws InterruptedException来识别阻塞方法。

一个重要的建议是避免使用不推荐使用的Thread.stop()方法。停止线程会使它解锁它已锁定的所有监视器。发生这种情况是由于ThreadDeath异常在堆栈上传播。

如果先前由这些监视器保护的任何对象处于不一致状态,则不一致的对象将对其他线程可见。这会导致很难发现和推理的任意行为。

5.结论

在本教程中,我们学习了在给定时间后停止执行的各种技术,以及每种技术的优缺点。

标签:

0 评论

发表评论

您的电子邮件地址不会被公开。 必填的字段已做标记 *