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


97

在Java中,有没有一种标准好的方法来调用带有超时的阻塞方法?我希望能够做到:

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

如果有道理。

谢谢。


1
作为参考,请查看Brian Goetz
pp。126-134中的

Answers:


151

您可以使用执行器:

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.get5秒钟后仍未返回,则抛出TimeoutException。可以以秒,分钟,毫秒为单位配置超时,也可以将其配置为单位TimeUnit

有关更多详细信息,请参见JavaDoc


13
即使超时后,阻塞方法也将继续运行,对吗?
伊万·杜布罗夫(Evan Dubrov)2009年

1
这取决于future.cancel。根据阻塞方法当时正在执行的操作,它可能会终止也可能不会终止。
skaffman 2009年

4
如何将参数传递给blockingMethod()?谢谢!
罗伯特·阿·亨鲁

@RobertAHenru:创建一个新类,该类BlockingMethodCallable的构造函数接受您要传递给它的参数blockingMethod()并将其存储为成员变量(可能是最终变量)。然后在内部call()将这些参数传递给blockMethod()
Vite Falcon

1
最后应该做的future.cancel(true)-类型Future <Object>中的方法cancel(boolean)不适用于参数()
Noam Manos



3

还有一个使用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并在构建生命周期中引入它。

有一篇文章进一步解释了它:限制Java方法执行时间


3

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

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

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

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

  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操作。

希望对您有所帮助


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

试试这个。更简单的解决方案。保证if块在规定时间内没有执行。该过程将终止并引发异常。

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

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

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

0

假设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)

希望能帮助到你。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.