在Java中,如何使用超时调用某些阻塞方法?

112
有没有一种标准的好方法在Java中调用带有超时的阻塞方法?我想要这样做:
// call something.blockingMethod();
// if it hasn't come back within 2 seconds, forget it

如果有意义的话。

谢谢。


1
作为参考,请查看Brian Goetz的《Java并发实践》第126-134页,特别是第6.3.7节“在任务上设置时间限制”。 - brown.2179
11个回答

171

你可以使用 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

16
超时后阻塞方法会继续运行,是吗? - Ivan Dubrov
1
这取决于 future.cancel。根据阻塞方法在执行时正在做什么,它可能会或可能不会终止。 - skaffman
4
如何将参数传递给blockingMethod()方法?谢谢! - Robert A Henru
@RobertAHenru:创建一个名为BlockingMethodCallable的新类,其构造函数接受您想要传递给blockingMethod()的参数,并将它们作为成员变量存储(可能是final)。然后在call()内部将这些参数传递给blockMethod() - Vite Falcon
1
最后应该执行 future.cancel(true) - 类型 Future<Object> 中的方法 cancel(boolean) 不适用于参数 ()。 - Noam Manos
显示剩余6条评论

9

2
FutureTask本身不是异步的,对吧?它只能同步地执行任务,你需要将其与Executor结合使用才能获得异步行为。 - skaffman

9

还可以参考Guava的TimeLimiter,它在幕后使用Executor。


3
好主意。但该链接已失效。Googlecode已不再存在。请尝试使用此链接:SimpleTimeLimiter - Rhubarb

8

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

大多数开发人员会尝试将阻塞调用放在不同的线程中,并设置一个future或某个计时器。但是,在Java中没有外部停止线程的方法,更不用说一些非常特定的情况,例如Thread.sleep()和Lock.lockInterruptibly()方法,它们明确处理线程中断。

因此,您只有三个通用选项:

  1. 将阻塞调用放在新线程中,如果时间到期,您只需继续运行,留下该线程。在这种情况下,您应确保线程被设置为守护线程。这样,该线程将不会阻止应用程序终止。

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

  3. 如果您的阻塞调用不是IO,而是逻辑,则可以重复检查Thread.isInterrupted()以检查是否已被外部中断,并使另一个线程在阻塞线程上调用thread.interrupt()

这门课程讲解并发编程,https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY。如果你想真正理解Java中的并发编程,它确实会深入讲解基础知识、特定限制和场景以及如何在其中操作。我个人尽可能避免使用阻塞调用进行编程。例如,有一些工具包,如Vert.x,可以使IO和非IO操作异步且非阻塞,从而提高性能。希望这可以帮到你。

3
我这里提供完整的代码。在我调用的方法位置,您可以使用您自己的方法:
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
        }
    }
}

3

此外,还有一个AspectJ解决方案,使用jcabi-aspects库。

@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
}

这是一段涉及IT技术的翻译内容,需要使用AspectJ来限制Java方法的执行时间。你需要在构建生命周期中引入AspectJ。以下是进一步解释的文章:Limit Java Method Execution Time


1
尝试这个。更简单的解决方案。保证如果代码块在时间限制内未执行,进程将终止并抛出异常。
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
    }

1
Thread thread = new Thread(new Runnable() {
    public void run() {
        something.blockingMethod();
    }
});
thread.start();
thread.join(2000);
if (thread.isAlive()) {
    thread.stop();
}

请注意,stop方法已被弃用,更好的替代方案是设置一些易失性布尔标志,在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)");
        }
    }
}

1
你需要一个类似于GitHub上failsafe项目中的断路器实现。请保留HTML标签,我已为你注意到了。

1

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

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接