Java:在某个代码块上设置超时?

Posted

技术标签:

【中文标题】Java:在某个代码块上设置超时?【英文标题】:Java: set timeout on a certain block of code? 【发布时间】:2011-08-08 14:01:46 【问题描述】:

是否可以在某些代码块运行时间超过可接受的时间后强制 Java 抛出异常?

【问题讨论】:

【参考方案1】:

这是我所知道的最简单的方法:

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();

【讨论】:

刚刚遇到这个。沿着这些路线做某事的开销是多少?我觉得如果你经常在 stuffToDo 中做一些事情,每次创建一个新的单线程执行器都是昂贵的,但我不知道因此我的问题。 我从未遇到过这种方法的性能问题。在某些情况下,最好创建自己的 Executor 实现,如果您认为可以通过特别关注只有一个线程的情况来制作更轻量级的版本。 ExecutorService 的性能在这里 (***.com/a/27025552/2116890) 得到了好评,被确定为非常棒。在那种情况下,他们正在处理更多线程。根据我的经验,无论是使用单个线程还是多个线程,我都没有注意到开销,但我也没有努力以任何方式衡量其相对于替代方案的性能。 executor.shutdownNow() 之后,您可能想要while (true) try if (executor.awaitTermination(1, TimeUnit.SECONDS)) break; catch (InterruptedException ie) ,因为实际停止任务可能需要一些时间。 这里要注意的有趣的事情。获得超时并不意味着底层任务已停止。未来被取消并且 executorService 知道这一点,但线程可能仍在工作而不关心终止,这意味着它可能仍然浪费您希望被释放的资源。【参考方案2】:

我将其他一些答案编译成一个实用方法:

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 分钟也是如此。 我现在添加了示例代码并改进了原始代码以在超时的情况下取消已经提交的任务。我将不胜感激;-) @htf,这确实应该是公认的答案。做得很好 @AliReza19330,是的,我的错。你说得对。不过,就性能而言,为每个用户启动一个新线程可能不是最好的主意。 :-P @NeemePraks 我认为“抛出(异常)e;”应该是“抛出(异常)t;”在第二个 runWithTimeout 方法中【参考方案3】:

是的,但是强制另一个线程在随机的代码行上中断通常是一个非常糟糕的主意。仅当您打算关闭该进程时,您才会这样做。

您可以做的是在一定时间后将Thread.interrupt() 用于某项任务。但是,除非代码对此进行检查,否则它将无法正常工作。 ExecutorService 可以通过Future.cancel(true) 使这更容易

让代码自己计时并在需要时停止会更好。

【讨论】:

问题是我有一个第三方库,有时运行时间过长,并且没有本地超时机制 不幸的是,这是一个常见问题,解决此问题的唯一可靠方法是拥有一个可以杀死的单独进程。另一种方法是在其上使用 Thread.stop() 。使用前请阅读此方法的警告!【参考方案4】:

如果是要计时的测试代码,可以使用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();

【讨论】:

【参考方案5】:

我可以建议两个选项。

    在方法内,假设它正在循环并且不等待外部事件,添加一个本地字段并测试每次循环的时间。

    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() 不能直接返回值,它必须更新实例字段以返回其值。

【讨论】:

+0:在你的第二个例子中,你是否试图在超时的情况下执行Thread.join(long) ;) thread.start() 之后你可以使用thread.join(10000); 而不是其余的代码。【参考方案6】:

编辑:Peter Lawrey 是完全正确的:它不像中断线程那么简单(我最初的建议),Executors & Callables 非常有用......

一旦达到超时,您可以在 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/Future.html#get-long-java.util.concurrent.TimeUnit-

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html

https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newFixedThreadPool%28int%29

【讨论】:

不幸的是,只有当你控制在可调用对象中执行的代码时才有效(如果这是真的,那么这样做是微不足道的) 好的,我现在可以在@HTF 的评论中看到这一点。我猜 Thread.stop() 是唯一的方法!在此处查看警告:download.oracle.com/javase/6/docs/technotes/guides/concurrency/…【参考方案7】:

我创建了一个非常简单的解决方案,没有使用任何框架或 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 连接挂起或完全中断。这导致整个系统崩溃。我需要一种方法来检测它并防止它发生。所以我创建了这个并使用了它。效果很好。

【讨论】:

【参考方案8】:

我遇到了类似的问题,我的任务是在特定超时内将消息推送到 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 文档说它确实保证执行将被阻止。

"尝试取消此任务的执行。如果该任务已经完成、已被取消或由于其他原因无法取消,则此尝试将失败。如果成功,则此任务调用cancel时尚未启动,该任务不应该运行。如果任务已经启动,则mayInterruptIfRunning参数决定是否应该中断执行该任务的线程以试图停止该任务。"

【讨论】:

【参考方案9】:

如果你想要一个 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 注释该方法,以便在抛出异常时重试该方法 3 次。

【讨论】:

【参考方案10】:

不要让新线程中的任务和主线程中的计时器,而是新线程中的计时器和主线程中的任务:

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 永远不会被使用。 在 main 方法中应该是 Thread timeOutThread = new Thread(timeOut); 然后使用 @Johannes timeOut。 注意,这是唯一可以扩展的解决方案。你真的只需要一个计时器线程,这可能会中断数千个工作线程。反过来,你有数千个定时器线程用于数千个工作线程 - 至少可以说并不理想。【参考方案11】:

有一种方法可以做到这一点。

设置一些布尔字段来指示工作是否完成。然后在代码块之前,设置一个计时器以在超时后运行一段代码。计时器将检查代码块是否已完成执行,如果没有,则抛出异常。否则它什么也做不了。

当然,代码块的末尾应该将该字段设置为 true 以指示工作已完成。

【讨论】:

这不是 100% 正确的,因为计时器线程将独立于运行代码块的线程。尽管“worker”线程什么也没做,但计时器线程可能会抛出异常,因为还有其他线程具有更高的优先级。 “运行时间超过 X 秒”和“在 X 秒之前开始”之间存在细微差别。 好的,很公平。感谢您的回复。我会留下答案,以便其他人可以看到您的评论【参考方案12】:

有一个非常简单的选项,还没有人提到:

Duration timeout = Duration.ofMinutes(5);
Thread thread = new Thread(() -> 
    // your code here
);
thread.start();
thread.join(timeout.toMillis());
if (thread.isAlive()) 
    thread.interrupt();
    throw new MyTimeoutException();

如果运行您的代码块的线程未能在超时时间内完成,它会被中断,并且可以抛出您想要的任何异常。

可以编写简单地忽略中断并继续执行的代码。如果您正在处理这个问题,则无法修复它,那么有thread.stop(),但这可能会破坏您所依赖的任何同步机制。见其deprecation notice。

您还可以从线程中捕获异常:

AtomicReference<Throwable> uncaughtException = new AtomicReference<>();
thread.setUncaughtExceptionHandler((t, ex) -> uncaughtException.setRelease(ex));

// ...

Throwable ex = uncaughtException.getAcquire();
if (ex != null) 
    throw ex;

【讨论】:

以上是关于Java:在某个代码块上设置超时?的主要内容,如果未能解决你的问题,请参考以下文章

在代码块上配置 OpenCV

如何在代码块上添加编译器标志

覆盖位置块上的 nginx http 版本

带有 var=Thread.currentThread() 的代码块上的 synchronized(...) [重复]

在数据块上运行 sql 查询时出现不匹配错误

如何使 CAGradientLayer 中的渐变跟随滑块上的某个值?