如何超时一个线程
我想运行一个线程一段固定的时间。 如果在这段时间内没有完成,我想要杀死它,抛出一些exception,或以某种方式处理它。 怎么办?
我从这个线程得出的一种方法是在Thread的run()方法中使用TimerTask。
有没有更好的解决scheme呢?
编辑:添加一个赏金,因为我需要一个更清晰的答案。 下面给出的ExecutorService代码不能解决我的问题。 为什么我应该睡觉()执行后(一些代码 – 我没有处理这段代码)? 如果代码完成并且sleep()被中断,怎么可能是一个超时?
需要执行的任务不在我的控制之下。 它可以是任何一段代码。 问题是这段代码可能会遇到无限循环。 我不希望这样的事情发生。 所以,我只想在一个单独的线程中运行这个任务。 父线程必须等到该线程完成并需要知道任务的状态(即是否超时或发生了某种exception或者是否成功)。 如果任务进入一个无限循环,我的父线程会一直等待,这不是一个理想的情况。
确实相反,使用ExecutorService
而不是Timer
,这里是一个SSCCE :
package com.stackoverflow.q2275443; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class Test { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(new Task()); try { System.out.println("Started.."); System.out.println(future.get(3, TimeUnit.SECONDS)); System.out.println("Finished!"); } catch (TimeoutException e) { future.cancel(true); System.out.println("Terminated!"); } executor.shutdownNow(); } } class Task implements Callable<String> { @Override public String call() throws Exception { Thread.sleep(4000); // Just to demo a long running task of 4 seconds. return "Ready!"; } }
在Future#get()
方法中使用timeout
参数,例如将它增加到5,您将看到线程完成。 您可以拦截catch (TimeoutException e)
块中的超时。
更新:澄清概念误解, sleep()
不是必需的。 它仅用于SSCCE /演示目的。 只要做你长期运行的任务就在那里sleep()
。 在长时间运行的任务中,您应该检查线程是否不中断 ,如下所示:
while (!Thread.interrupted()) { // Do your long running task here. }
对于任何旧的任务,没有一个100%可靠的方法来做到这一点。 这个任务必须铭记这个能力。
像ExecutorService
这样的核心Java库取消了在工作线程上使用interrupt()
调用的asynchronous任务。 因此,例如,如果任务包含某种循环,则应在每次迭代中检查其中断状态 。 如果任务正在进行I / O操作,那么它们也应该是可以中断的,而设置起来可能会非常棘手。 在任何情况下,请记住,代码必须主动检查中断; 设置中断不一定会做任何事情。
当然,如果你的任务是一个简单的循环,你可以检查每次迭代的当前时间,并在指定的超时时间结束后放弃。 在这种情况下,不需要工作者线程。
考虑使用ExecutorService的一个实例。 invokeAll()
和invokeAny()
方法都有timeout
参数。
当前线程将阻塞,直到方法完成(不确定是否需要),因为任务正常完成或达到超时。 你可以检查返回的Future
以确定发生了什么。
假设线程代码超出了你的控制范围:
从上面提到的Java 文档 :
如果一个线程不响应Thread.interrupt会怎么样?
在某些情况下,您可以使用特定于应用程序的技巧。 例如,如果某个线程正在等待已知的套接字,则可以closures套接字以使该线程立即返回。 不幸的是,实际上没有任何一般的技术可行。 应该注意的是,在所有等待线程都不响应Thread.interrupt的情况下,它也不会响应Thread.stop。 这种情况包括故意的拒绝服务攻击,以及thread.stop和thread.interrupt不能正常工作的I / O操作。
底线:
确保所有的线程都可以被中断,否则你需要特定的线程知识 – 比如有一个标志被设置。 也许你可以要求把任务和停止它的代码一起给你 – 用stop()
方法定义一个接口。 你也可以警告你什么时候不能停止任务。
BalusC说:
更新:澄清概念误解,sleep()不是必需的。 它仅用于SSCCE /演示目的。 只要做你长期运行的任务就在那里睡觉()。
但是,如果您replaceThread.sleep(4000);
用for (int i = 0; i < 5E8; i++) {}
那么它不会编译,因为空循环不会抛出InterruptedException
。
而对于线程是可中断的,它需要抛出一个InterruptedException
。
这对我来说似乎是一个严重的问题。 我看不出如何使这个答案适应一般的长期任务。
编辑补充:我重新这个问题作为一个新的问题:[ 中断固定时间后的线程,是否必须抛出InterruptedException? ]
我认为你应该看看适当的并发处理机制(线程运行到无限循环听起来不错,顺便说一句)。 确保你读了一些关于“杀死”或“停止”主题的话题。
你所描述的,听起来很像“集合点”,所以你可能想看看CyclicBarrier 。
可能有其他的结构(比如使用CountDownLatch )可以解决你的问题(一个线程等待一个超时的锁存器,另一个应该向下计数,如果locking已经完成了,它会释放你的第一个线程,超时或locking倒数计时被调用)。
我通常推荐两本书: Java中的并发编程和实践中的Java并发 。
我刚刚为此创build了一个辅助类。 伟大的作品:
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; /** * TimeOut class - used for stopping a thread that is taking too long * @author Peter Goransson * */ public class TimeOut { Thread interrupter; Thread target; long timeout; boolean success; boolean forceStop; CyclicBarrier barrier; /** * * @param target The Runnable target to be executed * @param timeout The time in milliseconds before target will be interrupted or stopped * @param forceStop If true, will Thread.stop() this target instead of just interrupt() */ public TimeOut(Runnable target, long timeout, boolean forceStop) { this.timeout = timeout; this.forceStop = forceStop; this.target = new Thread(target); this.interrupter = new Thread(new Interrupter()); barrier = new CyclicBarrier(2); // There will always be just 2 threads waiting on this barrier } public boolean execute() throws InterruptedException { // Start target and interrupter target.start(); interrupter.start(); // Wait for target to finish or be interrupted by interrupter target.join(); interrupter.interrupt(); // stop the interrupter try { barrier.await(); // Need to wait on this barrier to make sure status is set } catch (BrokenBarrierException e) { // Something horrible happened, assume we failed success = false; } return success; // status is set in the Interrupter inner class } private class Interrupter implements Runnable { Interrupter() {} public void run() { try { Thread.sleep(timeout); // Wait for timeout period and then kill this target if (forceStop) { target.stop(); // Need to use stop instead of interrupt since we're trying to kill this thread } else { target.interrupt(); // Gracefully interrupt the waiting thread } System.out.println("done"); success = false; } catch (InterruptedException e) { success = true; } try { barrier.await(); // Need to wait on this barrier } catch (InterruptedException e) { // If the Child and Interrupter finish at the exact same millisecond we'll get here // In this weird case assume it failed success = false; } catch (BrokenBarrierException e) { // Something horrible happened, assume we failed success = false; } } } }
这是这样调用的:
long timeout = 10000; // number of milliseconds before timeout TimeOut t = new TimeOut(new PhotoProcessor(filePath, params), timeout, true); try { boolean sucess = t.execute(); // Will return false if this times out if (!sucess) { // This thread timed out } else { // This thread ran completely and did not timeout } } catch (InterruptedException e) {}
我发布了一段代码,告诉你如何解决这个问题。 例如,我正在阅读一个文件。 您可以将此方法用于其他操作,但您需要实施kill()方法,以便主操作将被中断。
希望它有帮助
import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; /** * Main class * * @author el * */ public class Main { /** * Thread which perform the task which should be timed out. * * @author el * */ public static class MainThread extends Thread { /** * For example reading a file. File to read. */ final private File fileToRead; /** * InputStream from the file. */ final private InputStream myInputStream; /** * Thread for timeout. */ final private TimeOutThread timeOutThread; /** * true if the thread has not ended. */ boolean isRunning = true; /** * true if all tasks where done. */ boolean everythingDone = false; /** * if every thing could not be done, an {@link Exception} may have * Happens. */ Throwable endedWithException = null; /** * Constructor. * * @param file * @throws FileNotFoundException */ MainThread(File file) throws FileNotFoundException { setDaemon(false); fileToRead = file; // open the file stream. myInputStream = new FileInputStream(fileToRead); // Instantiate the timeout thread. timeOutThread = new TimeOutThread(10000, this); } /** * Used by the {@link TimeOutThread}. */ public void kill() { if (isRunning) { isRunning = false; if (myInputStream != null) { try { // close the stream, it may be the problem. myInputStream.close(); } catch (IOException e) { // Not interesting System.out.println(e.toString()); } } synchronized (this) { notify(); } } } /** * The task which should be timed out. */ @Override public void run() { timeOutThread.start(); int bytes = 0; try { // do something while (myInputStream.read() >= 0) { // may block the thread. myInputStream.read(); bytes++; // simulate a slow stream. synchronized (this) { wait(10); } } everythingDone = true; } catch (IOException e) { endedWithException = e; } catch (InterruptedException e) { endedWithException = e; } finally { timeOutThread.kill(); System.out.println("-->read " + bytes + " bytes."); isRunning = false; synchronized (this) { notifyAll(); } } } } /** * Timeout Thread. Kill the main task if necessary. * * @author el * */ public static class TimeOutThread extends Thread { final long timeout; final MainThread controlledObj; TimeOutThread(long timeout, MainThread controlledObj) { setDaemon(true); this.timeout = timeout; this.controlledObj = controlledObj; } boolean isRunning = true; /** * If we done need the {@link TimeOutThread} thread, we may kill it. */ public void kill() { isRunning = false; synchronized (this) { notify(); } } /** * */ @Override public void run() { long deltaT = 0l; try { long start = System.currentTimeMillis(); while (isRunning && deltaT < timeout) { synchronized (this) { wait(Math.max(100, timeout - deltaT)); } deltaT = System.currentTimeMillis() - start; } } catch (InterruptedException e) { // If the thread is interrupted, // you may not want to kill the main thread, // but probably yes. } finally { isRunning = false; } controlledObj.kill(); } } /** * Start the main task and wait for the end. * * @param args * @throws FileNotFoundException */ public static void main(String[] args) throws FileNotFoundException { long start = System.currentTimeMillis(); MainThread main = new MainThread(new File(args[0])); main.start(); try { while (main.isRunning) { synchronized (main) { main.wait(1000); } } long stop = System.currentTimeMillis(); if (main.everythingDone) System.out.println("all done in " + (stop - start) + " ms."); else { System.out.println("could not do everything in " + (stop - start) + " ms."); if (main.endedWithException != null) main.endedWithException.printStackTrace(); } } catch (InterruptedException e) { System.out.println("You've killed me!"); } } }
问候
以下片段将在一个单独的线程中启动一个操作,然后等待10秒钟以完成操作。 如果操作没有及时完成,代码将试图取消操作,然后继续其快乐的方式。 即使操作不能轻易取消,父线程也不会等待子线程终止。
ExecutorService executorService = getExecutorService(); Future<SomeClass> future = executorService.submit(new Callable<SomeClass>() { public SomeClass call() { // Perform long-running task, return result. The code should check // interrupt status regularly, to facilitate cancellation. } }); try { // Real life code should define the timeout as a constant or // retrieve it from configuration SomeClass result = future.get(10, TimeUnit.SECONDS); // Do something with the result } catch (TimeoutException e) { future.cancel(true); // Perform other error handling, eg logging, throwing an exception }
getExecutorService()
方法可以通过多种方式实现。 如果你没有特别的要求,你可以简单地调用Executors.newCachedThreadPool()
来进行线程池化,线程数没有上限。
有一件我没有提到的事情是杀死线程通常是一个坏主意。 有一些技术可以使线程方法干净地放弃 ,但这与在超时之后杀死一个线程不同。
你所暗示的风险在于,你可能不知道当杀死线程时线程会处于什么状态 – 所以你可能会冒险引入不稳定。 一个更好的解决scheme是确保你的线程代码不会自己挂起,或者会很好地响应中止请求。
我认为答案主要取决于任务本身。
- 它是否一遍又一遍地完成一项任务?
- 是否有必要在超时之后立即中断正在运行的任务?
如果第一个答案是肯定的,第二个答案是否定的,你可以保持这样简单:
public class Main { private static final class TimeoutTask extends Thread { private final long _timeoutMs; private Runnable _runnable; private TimeoutTask(long timeoutMs, Runnable runnable) { _timeoutMs = timeoutMs; _runnable = runnable; } @Override public void run() { long start = System.currentTimeMillis(); while (System.currentTimeMillis() < (start + _timeoutMs)) { _runnable.run(); } System.out.println("execution took " + (System.currentTimeMillis() - start) +" ms"); } } public static void main(String[] args) throws Exception { new TimeoutTask(2000L, new Runnable() { @Override public void run() { System.out.println("doing something ..."); try { // pretend it's taking somewhat longer than it really does Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } } }).start(); } }
如果这不是一个选项,请缩小您的要求 – 或者显示一些代码。
这里是我真的很简单的使用帮助类来运行或调用一段Java代码:-)
这是基于BalusC的出色答案
package com.mycompany.util.concurrent; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** * Calling {@link Callable#call()} or Running {@link Runnable#run()} code * with a timeout based on {@link Future#get(long, TimeUnit))} * @author pascaldalfarra * */ public class CallableHelper { private CallableHelper() { } public static final void run(final Runnable runnable, int timeoutInSeconds) { run(runnable, null, timeoutInSeconds); } public static final void run(final Runnable runnable, Runnable timeoutCallback, int timeoutInSeconds) { call(new Callable<Void>() { @Override public Void call() throws Exception { runnable.run(); return null; } }, timeoutCallback, timeoutInSeconds); } public static final <T> T call(final Callable<T> callable, int timeoutInSeconds) { return call(callable, null, timeoutInSeconds); } public static final <T> T call(final Callable<T> callable, Runnable timeoutCallback, int timeoutInSeconds) { ExecutorService executor = Executors.newSingleThreadExecutor(); try { Future<T> future = executor.submit(callable); T result = future.get(timeoutInSeconds, TimeUnit.SECONDS); System.out.println("CallableHelper - Finished!"); return result; } catch (TimeoutException e) { System.out.println("CallableHelper - TimeoutException!"); if(timeoutCallback != null) { timeoutCallback.run(); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } finally { executor.shutdownNow(); executor = null; } return null; } }
BalusC的很好的答案:
但只是补充说,超时本身不会中断线程本身。 即使您正在使用while(!Thread.interrupted())检查您的任务。 如果要确保线程已停止,则还应确保future.cancel()在超时exception被捕获时调用。
package com.stackoverflow.q2275443; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class Test { public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<String> future = executor.submit(new Task()); try { System.out.println("Started.."); System.out.println(future.get(3, TimeUnit.SECONDS)); System.out.println("Finished!"); } catch (TimeoutException e) { //Without the below cancel the thread will continue to live // even though the timeout exception thrown. future.cancel(); System.out.println("Terminated!"); } executor.shutdownNow(); } } class Task implements Callable<String> { @Override public String call() throws Exception { while(!Thread.currentThread.isInterrupted()){ System.out.println("Im still running baby!!"); } } }
我正在寻找一个ExecutorService,可以中断所有由它执行的超时Runnables,但没有发现任何。 几个小时后,我创build一个如下。 这个类可以被修改来增强健壮性。
public class TimedExecutorService extends ThreadPoolExecutor { long timeout; public TimedExecutorService(int numThreads, long timeout, TimeUnit unit) { super(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(numThreads + 1)); this.timeout = unit.toMillis(timeout); } @Override protected void beforeExecute(Thread thread, Runnable runnable) { Thread interruptionThread = new Thread(new Runnable() { @Override public void run() { try { // Wait until timeout and interrupt this thread Thread.sleep(timeout); System.out.println("The runnable times out."); thread.interrupt(); } catch (InterruptedException e) { e.printStackTrace(); } } }); interruptionThread.start(); } }
用法:
public static void main(String[] args) { Runnable abcdRunnable = new Runnable() { @Override public void run() { System.out.println("abcdRunnable started"); try { Thread.sleep(20000); } catch (InterruptedException e) { // logger.info("The runnable times out."); } System.out.println("abcdRunnable ended"); } }; Runnable xyzwRunnable = new Runnable() { @Override public void run() { System.out.println("xyzwRunnable started"); try { Thread.sleep(20000); } catch (InterruptedException e) { // logger.info("The runnable times out."); } System.out.println("xyzwRunnable ended"); } }; int numThreads = 2, timeout = 5; ExecutorService timedExecutor = new TimedExecutorService(numThreads, timeout, TimeUnit.SECONDS); timedExecutor.execute(abcdRunnable); timedExecutor.execute(xyzwRunnable); timedExecutor.shutdown(); }
现在,我遇到这样的问题。 它恰好解码图片。 解码过程花费太多时间,屏幕保持黑屏。 添加一个时间控制器:当时间太长,然后从当前线程popup。 以下是差异:
ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Bitmap> future = executor.submit(new Callable<Bitmap>() { @Override public Bitmap call() throws Exception { Bitmap bitmap = decodeAndScaleBitmapFromStream(context, inputUri);// do some time consuming operation return null; } }); try { Bitmap result = future.get(1, TimeUnit.SECONDS); } catch (TimeoutException e){ future.cancel(true); } executor.shutdown(); return (bitmap!= null);
我有同样的问题。 所以我想出了一个简单的解决scheme。
public class TimeoutBlock { private final long timeoutMilliSeconds; private long timeoutInteval=100; public TimeoutBlock(long timeoutMilliSeconds){ this.timeoutMilliSeconds=timeoutMilliSeconds; } public void addBlock(Runnable runnable) throws Throwable{ long collectIntervals=0; Thread timeoutWorker=new Thread(runnable); timeoutWorker.start(); do{ if(collectIntervals>=this.timeoutMilliSeconds){ timeoutWorker.stop(); throw new Exception("<<<<<<<<<<****>>>>>>>>>>> Timeout Block Execution Time Exceeded In "+timeoutMilliSeconds+" Milli Seconds. Thread Block Terminated."); } collectIntervals+=timeoutInteval; Thread.sleep(timeoutInteval); }while(timeoutWorker.isAlive()); System.out.println("<<<<<<<<<<####>>>>>>>>>>> Timeout Block Executed Within "+collectIntervals+" Milli Seconds."); } /** * @return the timeoutInteval */ public long getTimeoutInteval() { return timeoutInteval; } /** * @param timeoutInteval the timeoutInteval to set */ public void setTimeoutInteval(long timeoutInteval) { this.timeoutInteval = timeoutInteval; } }
保证,如果在时限内没有执行阻止。 该进程将终止并引发exception。
例如:
try { TimeoutBlock timeoutBlock = new TimeoutBlock(10 * 60 * 1000);//set timeout in milliseconds Runnable block=new Runnable() { @Override public void run() { //TO DO write block of code } }; timeoutBlock.addBlock(block);// execute the runnable block } catch (Throwable e) { //catch the exception here . Which is block didn't execute within the time limit }