如何在Java中經過一定時間後停止執行
- 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
方法將阻塞主線程直到指定的時間。在指定的超時後,它將引發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.結論
在本教程中,我們學習了在給定時間後停止執行的各種技術,以及每種技術的優缺點。