在某些代码块的运行时间超过可接受的时间之后,是否可以强制Java引发Exception?
Answers:
是的,但是强制另一个线程在随机的代码行上中断通常是一个非常糟糕的主意。仅当您打算关闭该过程时才这样做。
您可以做的是Thread.interrupt()
在一定时间后用于任务。但是,除非代码对此进行检查,否则它将无法正常工作。ExecutorService可以使此操作更容易Future.cancel(true)
代码自动计时并在需要时停止会更好。
这是我所知道的最简单的方法:
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();
executor.shutdownNow()
,您可能想while (true) {try {if (executor.awaitTermination(1, TimeUnit.SECONDS)) break;} catch (InterruptedException ie) {}}
要这样做,因为实际停止任务可能需要一些时间。
我将其他一些答案编译为一个实用程序方法:
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!
runWithTimeout
即使我将超时设置为150分钟,它也会在执行该方法后立即引发异常。
如果是您想计时的测试代码,则可以使用以下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 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();
}
我可以提出两个选择。
在该方法中,假设它正在循环并且不等待外部事件,则添加一个本地字段并在每次循环时测试时间。
void method() {
long endTimeMillis = System.currentTimeMillis() + 10000;
while (true) {
// method logic
if (System.currentTimeMillis() > endTimeMillis) {
// do some clean-up
return;
}
}
}
在线程中运行该方法,并使调用者计数为10秒。
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) {}
}
这种方法的缺点是method()无法直接返回值,它必须更新实例字段以返回其值。
Thread.join(long)
使用超时;)
thread.start()
您可以拥有thread.join(10000);
其余的代码。
编辑:彼得·劳里(Peter Lawrey)完全正确:它不像中断线程那样简单(我的最初建议),执行器和可调用对象非常有用...
无需中断线程,而是可以在达到超时后在Callable上设置一个变量。可调用对象应在任务执行的适当位置检查此变量,以了解何时停止。
可赎回债券返回期货,您可以在尝试“获取”期货结果时指定超时。像这样:
try {
future.get(timeoutSeconds, TimeUnit.SECONDS)
} catch(InterruptedException e) {
myCallable.setStopMeAtAppropriatePlace(true);
}
请参见Future.get,执行程序和可调用...
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。我使用了琐碎的逻辑,即通过另一个线程执行它,并通过指定超时来等待其将来的对象。如果超时,这将给我一个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");
}
但是在某些情况下,您无法停止由另一个线程执行的代码,在这种情况下,您会得到真正的底片。
例如-就我而言,我的请求到达了SQS,并且在推送消息时,我的代码逻辑遇到了指定的超时。现在,实际上我的消息已被推送到队列中,但由于TIMEOUT异常,我的主线程认为该消息失败。这是可以避免而不是解决的问题。就像我的情况一样,我通过提供一个在几乎所有情况下都足够的超时来避免这种情况。
如果您要中断的代码在应用程序内部,并且不像API调用,那么您可以简单地使用
future.cancel(true)
但是请记住,java docs说它确实保证了执行将被阻止。
“尝试取消执行此任务。如果任务已经完成,已经被取消或由于其他原因无法取消,则此尝试将失败。如果成功,并且在调用cancel时此任务尚未开始,则此操作将失败。任务永远不会运行。如果任务已经启动,则mayInterruptIfRunning参数确定执行该任务的线程是否应在尝试停止该任务时被中断。
如果您想使用CompletableFuture方法,可以使用类似
public 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);
}
}
如果您使用的是spring,则可以使用注释该方法,@Retryable
以便在引发异常时重试该方法三次。
而不是将任务放在新线程中并将计时器放在主线程中,而应将计时器放在新线程中并将任务放在主线程中:
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 }
}
TimeOut timeOut
的main()
方法,从未使用过。
Thread timeOutThread = new Thread(timeOut);
@Johannes timeOut。
有一种方法可以做到。
设置一些布尔值字段以指示工作是否完成。然后在代码块之前,设置一个计时器,以在超时后运行一段代码。计时器将检查代码块是否已完成执行,否则,将引发异常。否则它将无能为力。
当然,代码块的末尾应将字段设置为true,以指示工作已完成。