【问题标题】:How do I call some blocking method with a timeout in Java?如何在 Java 中调用一些带有超时的阻塞方法?
【发布时间】:2023-03-31 15:14:01
【问题描述】:

在 Java 中是否有标准的好方法来调用具有超时的阻塞方法?我希望能够做到:

// call something.blockingMethod();
// if it hasn't come back within 2 seconds, forget it

如果有道理的话。

谢谢。

【问题讨论】:

  • 作为参考,请查看 Brian Goetz 的 Java Concurrency in Practice pp. 126 - 134,特别是第 6.3.7 节“对任务设置时间限制”

标签: java concurrency


【解决方案1】:

你可以使用 Executor:

ExecutorService executor = Executors.newCachedThreadPool();
Callable<Object> task = new Callable<Object>() {
   public Object call() {
      return something.blockingMethod();
   }
};
Future<Object> future = executor.submit(task);
try {
   Object result = future.get(5, TimeUnit.SECONDS); 
} catch (TimeoutException ex) {
   // handle the timeout
} catch (InterruptedException e) {
   // handle the interrupts
} catch (ExecutionException e) {
   // handle other exceptions
} finally {
   future.cancel(true); // may or may not desire this
}

如果future.get 在 5 秒内没有返回,它会抛出一个TimeoutException。超时时间可以以秒、分钟、毫秒或TimeUnit 中的常量可用的任何单位进行配置。

更多详情请参阅JavaDoc

【讨论】:

  • 阻塞方法超时后还会继续运行,对吧?
  • 这取决于future.cancel。根据当时阻塞方法正在做什么,它可能会也可能不会终止。
  • 如何将参数传递给 blockingMethod() ?谢谢!
  • @RobertAHenru:创建一个名为BlockingMethodCallable 的新类,其构造函数接受您要传递给blockingMethod() 的参数并将它们存储为成员变量(可能作为最终变量)。然后在call() 内部将这些参数传递给blockMethod()
  • 最后应该做future.cancel(true) - Future 类型中的方法 cancel(boolean) 不适用于参数 ()
【解决方案2】:

您可以将调用包装在 FutureTask 中并使用 get() 的超时版本。

http://java.sun.com/j2se/1.5.0/docs/api/java/util/concurrent/FutureTask.html

【讨论】:

  • FutureTask 本身不是异步的,是吗?就其本身而言,它只是同步执行操作,您需要将其与 Executor 结合起来以获取异步行为。
【解决方案3】:

另请参阅 Guava 的 TimeLimiter,它在幕后使用 Executor。

【讨论】:

  • 好主意。但是链接是陈旧的。 Googlecode 已不复存在.. 尝试此链接到 SimpleTimeLimiter
【解决方案4】:

人们尝试以多种方式实现这一点真是太棒了。但事实是,没有办法。

大多数开发人员会尝试将阻塞调用放在不同的线程中,并有一个未来或一些计时器。但是在 Java 中没有办法从外部停止线程,更不用说一些非常特殊的情况,比如显式处理线程中断的 Thread.sleep() 和 Lock.lockInterruptibly() 方法。

所以实际上你只有 3 个通用选项:

  1. 将您的阻塞调用放在一个新线程上,如果时间到期,您只需继续前进,让该线程挂起。在这种情况下,您应该确保将线程设置为守护线程。这样线程就不会阻止您的应用程序终止。

  2. 使用非阻塞 Java API。因此,例如对于网络,使用 NIO2 并使用非阻塞方法。要从控制台读取,请在阻塞等之前使用 Scanner.hasNext()。

  3. 1234563 p>

这门关于并发的课程https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

如果您真的想了解它在 Java 中的工作原理,那么您将真正了解这些基础知识。它实际上谈到了这些特定的限制和场景,以及如何在其中一堂课中解决这些问题。

我个人尽量在不使用阻塞调用的情况下进行编程。例如,有像 Vert.x 这样的工具包,可以非常轻松地以非阻塞方式异步执行 IO 和无 IO 操作。

希望对你有帮助

【讨论】:

    【解决方案5】:

    还有一个带有jcabi-aspects 库的AspectJ 解决方案。

    @Timeable(limit = 30, unit = TimeUnit.MINUTES)
    public Soup cookSoup() {
      // Cook soup, but for no more than 30 minutes (throw and exception if it takes any longer
    }
    

    它再简洁不过了,但是您当然必须依赖 AspectJ 并将其引入您的构建生命周期中。

    有一篇文章进一步解释:Limit Java Method Execution Time

    【讨论】:

      【解决方案6】:

      我在这里给你完整的代码。代替我调用的方法,您可以使用您的方法:

      public class NewTimeout {
          public String simpleMethod() {
              return "simple method";
          }
      
          public static void main(String[] args) {
              ExecutorService executor = Executors.newSingleThreadScheduledExecutor();
              Callable<Object> task = new Callable<Object>() {
                  public Object call() throws InterruptedException {
                      Thread.sleep(1100);
                      return new NewTimeout().simpleMethod();
                  }
              };
              Future<Object> future = executor.submit(task);
              try {
                  Object result = future.get(1, TimeUnit.SECONDS); 
                  System.out.println(result);
              } catch (TimeoutException ex) {
                  System.out.println("Timeout............Timeout...........");
              } catch (InterruptedException e) {
                  // handle the interrupts
              } catch (ExecutionException e) {
                  // handle other exceptions
              } finally {
                  executor.shutdown(); // may or may not desire this
              }
          }
      }
      

      【讨论】:

        【解决方案7】:
        Thread thread = new Thread(new Runnable() {
            public void run() {
                something.blockingMethod();
            }
        });
        thread.start();
        thread.join(2000);
        if (thread.isAlive()) {
            thread.stop();
        }
        

        注意,不推荐使用 stop,更好的选择是设置一些 volatile 布尔标志,在 blockingMethod() 内部检查它并退出,如下所示:

        import org.junit.*;
        import java.util.*;
        import junit.framework.TestCase;
        
        public class ThreadTest extends TestCase {
            static class Something implements Runnable {
                private volatile boolean stopRequested;
                private final int steps;
                private final long waitPerStep;
        
                public Something(int steps, long waitPerStep) {
                    this.steps = steps;
                    this.waitPerStep = waitPerStep;
                }
        
                @Override
                public void run() {
                    blockingMethod();
                }
        
                public void blockingMethod() {
                    try {
                        for (int i = 0; i < steps && !stopRequested; i++) {
                            doALittleBit();
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
        
                public void doALittleBit() throws InterruptedException {
                    Thread.sleep(waitPerStep);
                }
        
                public void setStopRequested(boolean stopRequested) {
                    this.stopRequested = stopRequested;
                }
            }
        
            @Test
            public void test() throws InterruptedException {
                final Something somethingRunnable = new Something(5, 1000);
                Thread thread = new Thread(somethingRunnable);
                thread.start();
                thread.join(2000);
                if (thread.isAlive()) {
                    somethingRunnable.setStopRequested(true);
                    thread.join(2000);
                    assertFalse(thread.isAlive());
                } else {
                    fail("Exptected to be alive (5 * 1000 > 2000)");
                }
            }
        }
        

        【讨论】:

          【解决方案8】:

          试试这个。更简单的解决方案。保证如果块没有在时间限制内执行。进程将终止并抛出异常。

          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 
                      }
                  };
          
                  timeoutBlock.addBlock(block);// execute the runnable block 
          
              } catch (Throwable e) {
                  //catch the exception here . Which is block didn't execute within the time limit
              }
          

          【讨论】:

            【解决方案9】:

            假设blockingMethod 只是睡了几毫秒:

            public void blockingMethod(Object input) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            

            我的解决方案是像这样使用wait()synchronized

            public void blockingMethod(final Object input, long millis) {
                final Object lock = new Object();
                new Thread(new Runnable() {
            
                    @Override
                    public void run() {
                        blockingMethod(input);
                        synchronized (lock) {
                            lock.notify();
                        }
                    }
                }).start();
                synchronized (lock) {
                    try {
                        // Wait for specific millis and release the lock.
                        // If blockingMethod is done during waiting time, it will wake
                        // me up and give me the lock, and I will finish directly.
                        // Otherwise, when the waiting time is over and the
                        // blockingMethod is still
                        // running, I will reacquire the lock and finish.
                        lock.wait(millis);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
            

            所以你可以替换

            something.blockingMethod(input)

            something.blockingMethod(input, 2000)

            希望对你有帮助。

            【讨论】:

              【解决方案10】:

              您需要一个 circuit breaker 实现,就像 GitHub 上的 failsafe 项目中的那个。

              【讨论】:

                【解决方案11】:

                在阻塞队列的特殊情况下:

                通用java.util.concurrent.SynchronousQueue 有一个带有超时参数的poll 方法。

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2013-11-16
                  • 1970-01-01
                  • 1970-01-01
                  • 2020-01-25
                  • 1970-01-01
                  • 2013-12-07
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多