有没有办法在一段代码块执行的时间超出可接受范围后强制Java抛出异常?
以下是具体步骤:
final Runnable stuffToDo = new Thread() {
@Override
public void run() {
/* Do stuff here. */
}
};
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future future = executor.submit(stuffToDo);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
future.get(5, TimeUnit.MINUTES);
}
catch (InterruptedException ie) {
/* Handle the interruption. Or ignore it. */
}
catch (ExecutionException ee) {
/* Handle the error. Or ignore it. */
}
catch (TimeoutException te) {
/* Handle the timeout. Or ignore it. */
}
if (!executor.isTerminated())
executor.shutdownNow(); // If you want to stop the code that hasn't finished.
另外,您可以创建一个TimeLimitedCodeBlock类来包装此功能,然后您可以在需要时使用它,如下所示:
new TimeLimitedCodeBlock(5, TimeUnit.MINUTES) { @Override public void codeBlock() {
// Do stuff here.
}}.run();
我将其他答案编译成一个单一的实用方法:
public class TimeLimitedCodeBlock {
public static void runWithTimeout(final Runnable runnable, long timeout, TimeUnit timeUnit) throws Exception {
runWithTimeout(new Callable<Object>() {
@Override
public Object call() throws Exception {
runnable.run();
return null;
}
}, timeout, timeUnit);
}
public static <T> T runWithTimeout(Callable<T> callable, long timeout, TimeUnit timeUnit) throws Exception {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<T> future = executor.submit(callable);
executor.shutdown(); // This does not cancel the already-scheduled task.
try {
return future.get(timeout, timeUnit);
}
catch (TimeoutException e) {
//remove this if you do not want to cancel the job in progress
//or set the argument to 'false' if you do not want to interrupt the thread
future.cancel(true);
throw e;
}
catch (ExecutionException e) {
//unwrap the root cause
Throwable t = e.getCause();
if (t instanceof Error) {
throw (Error) t;
} else if (t instanceof Exception) {
throw (Exception) t;
} else {
throw new IllegalStateException(t);
}
}
}
}
示例代码:
public static void main(String[] args) throws Exception {
final long startTime = System.currentTimeMillis();
log(startTime, "calling runWithTimeout!");
try {
TimeLimitedCodeBlock.runWithTimeout(new Runnable() {
@Override
public void run() {
try {
log(startTime, "starting sleep!");
Thread.sleep(10000);
log(startTime, "woke up!");
}
catch (InterruptedException e) {
log(startTime, "was interrupted!");
}
}
}, 5, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
log(startTime, "got timeout!");
}
log(startTime, "end of main method!");
}
private static void log(long startTime, String msg) {
long elapsedSeconds = (System.currentTimeMillis() - startTime);
System.out.format("%1$5sms [%2$16s] %3$s\n", elapsedSeconds, Thread.currentThread().getName(), msg);
}
在我的计算机上运行示例代码的输出:
0ms [ main] calling runWithTimeout!
13ms [ pool-1-thread-1] starting sleep!
5015ms [ main] got timeout!
5016ms [ main] end of main method!
5015ms [ pool-1-thread-1] was interrupted!
是的,但通常强制另一个线程在代码的任意行中断是一个非常糟糕的主意。只有在想要关闭进程时才会这样做。
你可以在一定时间后使用Thread.interrupt()
来终止一个任务。但是,除非代码对此进行检查,否则它将不起作用。使用Future.cancel(true)
可以让ExecutorService更容易实现此目的。
最好的做法是让代码自行计时并在需要时停止。
如果你想测试代码的运行时间,那么你可以使用time
属性:
@Test(timeout = 1000)
public void shouldTakeASecondOrLess()
{
}
如果这是生产代码,那么就没有简单的机制,你可以使用哪种解决方案取决于你是否可以改变代码以进行计时。
如果您可以更改被计时的代码,则一种简单的方法是让您的计时代码记住其开始时间,并定期将当前时间与该时间进行比较。例如:
long startTime = System.currentTimeMillis();
// .. do stuff ..
long elapsed = System.currentTimeMillis()-startTime;
if (elapsed>timeout)
throw new RuntimeException("tiomeout");
如果代码本身无法检查超时,您可以在另一个线程上执行代码,并等待完成或超时。
Callable<ResultType> run = new Callable<ResultType>()
{
@Override
public ResultType call() throws Exception
{
// your code to be timed
}
};
RunnableFuture<ResultType> future = new FutureTask<>(run);
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(future);
ResultType result = null;
try
{
result = future.get(1, TimeUnit.SECONDS); // wait 1 second
}
catch (TimeoutException ex)
{
// timed out. Try to stop the code if possible.
future.cancel(true);
}
service.shutdown();
}
Within the method, assuming it is looping and not waiting for an external event, add a local field and test the time each time around the loop.
void method() {
long endTimeMillis = System.currentTimeMillis() + 10000;
while (true) {
// method logic
if (System.currentTimeMillis() > endTimeMillis) {
// do some clean-up
return;
}
}
}
Run the method in a thread, and have the caller count to 10 seconds.
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
method();
}
});
thread.start();
long endTimeMillis = System.currentTimeMillis() + 10000;
while (thread.isAlive()) {
if (System.currentTimeMillis() > endTimeMillis) {
// set an error flag
break;
}
try {
Thread.sleep(500);
}
catch (InterruptedException t) {}
}
Thread.join(long)
并设置超时时间呢? ;) - Peter Lawreythread.start()
之后,你可以使用 thread.join(10000)
替换掉剩余的代码。 - Peter Lawrey编辑:Peter Lawrey是完全正确的:中断线程并不像我最初的建议那样简单,而且Executors和Callables非常有用...
与其中断线程,你可以在Callable上设置一个变量,一旦达到超时时间就会触发。Callable应该在任务执行的适当时点检查此变量,以知道何时停止。
Callables返回Futures,在尝试“获取”未来结果时,您可以指定超时时间。类似于这样:
try {
future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
myCallable.setStopMeAtAppropriatePlace(true);
}
请参见 Future.get、Executors 和 Callable...
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html
我创建了一个非常简单的解决方案,没有使用任何框架或API。这看起来更优雅和易懂。该类被称为TimeoutBlock。
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;
}
}
例子:
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 to execute
}
};
timeoutBlock.addBlock(block);// execute the runnable block
} catch (Throwable e) {
//catch the exception here . Which is block didn't execute within the time limit
}
当我需要连接FTP帐户并下载和上传文件时,这对我非常有用。有时候FTP连接会卡住或完全中断,导致整个系统崩溃。我需要一种检测它并防止发生的方法。所以我创建了这个并使用了它。效果相当不错。
我曾经遇到类似的问题,我的任务是在特定的超时时间内将消息推送到SQS。我使用了简单的逻辑,通过另一个线程执行并等待其future对象,并指定超时时间。如果超时,这将给我一个TIMEOUT异常。
final Future<ISendMessageResult> future =
timeoutHelperThreadPool.getExecutor().submit(() -> {
return getQueueStore().sendMessage(request).get();
});
try {
sendMessageResult = future.get(200, TimeUnit.MILLISECONDS);
logger.info("SQS_PUSH_SUCCESSFUL");
return true;
} catch (final TimeoutException e) {
logger.error("SQS_PUSH_TIMEOUT_EXCEPTION");
}
future.cancel(true)
将计时器放在新线程中,将任务放在主线程中,而不是将任务放在新线程中,将计时器放在主线程中:
public static class TimeOut implements Runnable{
public void run() {
Thread.sleep(10000);
if(taskComplete ==false) {
System.out.println("Timed Out");
return;
}
else {
return;
}
}
}
public static boolean taskComplete = false;
public static void main(String[] args) {
TimeOut timeOut = new TimeOut();
Thread timeOutThread = new Thread(timeOut);
timeOutThread.start();
//task starts here
//task completed
taskComplete =true;
while(true) {//do all other stuff }
}
main()
方法中定义的变量 TimeOut timeOut
从未被使用。 - JohannesThread timeOutThread = new Thread(timeOut);
然后使用 @Johannes 的 timeOut。 - Arjun Varshneypublic MyResponseObject retrieveDataFromEndpoint() {
CompletableFuture<MyResponseObject> endpointCall
= CompletableFuture.supplyAsync(() ->
yourRestService.callEnpoint(withArg1, withArg2));
try {
return endpointCall.get(10, TimeUnit.MINUTES);
} catch (TimeoutException
| InterruptedException
| ExecutionException e) {
throw new RuntimeException("Unable to fetch data", e);
}
}
@Retryable
注释该方法,以便在抛出异常时重试该方法三次。
executor.shutdownNow()
后,你可能想要使用while (true) {try {if (executor.awaitTermination(1, TimeUnit.SECONDS)) break;} catch (InterruptedException ie) {}}
,因为实际停止任务可能需要一些时间。 - Anders Kaseorg