如何在Java中經過一定時間後停止執行

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.結論

在本教程中,我們學習了在給定時間後停止執行的各種技術,以及每種技術的優缺點。