Java - 获取超时的最佳方式

3

我有这样的代码:

while(isResponseArrived)
   Thread.yield();

但是我真正想做的是这样的:
long startTime = System.currentTimeInMilliseconds();

while(isResponseArrived)
{
   if(isTimeoutReached(startTime))
       throw new TimeOutExcepton(); 
   Thread.yield();
}

我还不确定是否需要抛出异常(这对这个问题来说不重要),但我想知道如何让它尽可能地高效,避免过多占用处理器资源。换句话说,我该如何使isTimeoutReached(long startTime)尽可能友好地提高性能。

我已经进行了测试:

for(int x=0; x<99999999; x++)
    System.nanoTime();

对比

for(int x=0; x<99999999; x++)
    System.currentTimeInMilliseconds();

差异很小,完成时间少于10%

我还尝试使用Thread.sleep(),但如果有更新且处理器正在等待,我真的想尽快通知用户。 Thread.yield()不会使处理器开始运行,它只是一个NOP,直到好了才会给其他人处理器优先级。

无论如何,有没有不限制CPU的最佳方法来测试超时?这是正确的方法吗?


你如何设置 isResponseArrived=true?如果它是由网络响应设置的,最优的方式将是使用超时读取输入通道。 - khachik
我不能这样做,因为一旦打开了网络通道,它会一直保持打开状态,直到我关闭它。这不像一个HTML调用,其中网络会立即打开和关闭,在这种情况下,连接始终保持打开状态,更像是SSH连接。但我想做的是限制任何一个命令所需的时间。 - Stephane Grenier
2个回答

2

根据我的经验,超时时间通常是任意选择的,因为它们不是时间关键型的。 如果我选择了1000毫秒的超时时间,而实际上需要1001毫秒完成,则影响应该微不足道。 对于实现超时,建议尽可能简单。

您可以使用ScheduledExecutorService来实现超时。

final ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();

public void addTimeoutForTask(final Future future, int timeOutMS) {
    ses.schedule(new Runnable() {
        @Override
        public void run() {
            future.cancel(true);
        }
    }, timeOutMS, TimeUnit.MILLISECONDS);
}

如果您正在执行一些非阻塞操作,并希望设置超时,可以执行以下操作。

interface TimedPoller {
    public void poll();

    /**
     * @return is it now closed.
     */
    public boolean checkTimeout(long nowNS);
}

private final Set<TimedPoller> timedPollers = new LinkedHashSet<>();
private volatile TimedPoller[] timedPollersArray = {};

public void add(TimedPoller timedPoller) {
    synchronized (timedPollers) {
        long nowNS = System.nanoTime();
        if (!timedPoller.checkTimeout(nowNS) && timedPollers.add(timedPoller))
            timedPollersArray = timedPollers.toArray(new TimedPoller[timedPollers.size());
    }
}

public void remove(TimedPoller timedPoller) {
    synchronized (timedPollers) {
        if (timedPollers.remove(timedPoller))
            timedPollersArray = timedPollers.toArray(new TimedPoller[timedPollers.size());
    }
}

private volatile boolean running = true;

public void run() {
    while (running) {
        // check the timeout for every 1000 polls.
        for (int i = 0; i < 1000; i += timedPollersArray.length) {
            TimedPoller[] pollers = timedPollersArray;
            for (TimedPoller poller : pollers) {
                poller.poll();
            }
        }
        long nowNS = System.nanoTime();
        TimedPoller[] pollers = timedPollersArray;
        for (TimedPoller poller : pollers) {
            if (poller.checkTimeout(nowNS))
                remove(poller);
        }
    }
}

要么你放弃了CPU,要么你不放弃。如果你放弃了CPU,其他线程可以运行,但是在下一次运行之前会有一定的延迟。或者你不放弃CPU,这会提高你的响应时间,但是另一个线程不能运行。
看起来你想让其他东西运行,而不付出放弃CPU的代价。这并不是微不足道的,但如果做得正确,可以给你带来两者的某些好处(如果效率低下,则两面都不如)。
你可以实现自己的线程逻辑,前提是你有大量的小任务。例如,如果你想轮询10件事情,你可以只使用一个CPU。

抱歉,我误读了问题。我假设您还想轮询一些IO。 - Peter Lawrey
我已经更新了我的答案,以便仅执行超时操作或使用超时执行非阻塞操作。 - Peter Lawrey
你在这种情况下为什么选择了wait而不是sleep呢?我知道它们非常相似但有微小的区别。我的理解是,如果处理器没有任务要执行,wait将继续执行,而sleep则表示在至少经过一定时间后再尝试。那么,wait不会通过时间测试消耗处理器吗? - Stephane Grenier
我猜你在谈论Evgeniy的解决方案,但在我看来,这是为了让notifyAll()能够提前唤醒它。wait是由操作系统在其核心实现的,不会使用更多的CPU。 - Peter Lawrey
1
是的,我在发表评论后才意识到这一点。这就是为什么通宵编程并不总是好事的原因 ;) - Stephane Grenier
显示剩余3条评论

2
我认为使用wait / notify会更高效。
boolean arrived;

public synchronized void waitForResponse(long timeout) throws InterruptedException, TimeoutException {
    long t0 = System.currentTimeMillis() + timeout;
    while (!arrived) {
        long delay = System.currentTimeMillis() - t0;
        if (delay < 0) {
            throw new TimeoutException();
        }
        wait(delay);
    }
}

public synchronized void responseArrived() {
    arrived = true;
    notifyAll();
}

有时可能会失败(来自wait()的文档):“线程也可以在没有被通知、中断或超时的情况下被唤醒[...]换句话说,等待应该始终发生在循环中。” - Julien
正确的,这只是一个想法,不是一个生产代码。有一个例子如何在Thread.join(long millis)中正确实现。 - Evgeniy Dorofeev
另一个问题是你将强制每个循环都是延迟时间。例如,如果你使用100毫秒,最小的时间增量就是100毫秒。 - Stephane Grenier
循环只是防止虚假唤醒的保护措施(请参见Object.wait API)。在现实生活中,没有循环,waitForResponse会在等待时阻塞,当响应到达或超时时会收到信号。 - Evgeniy Dorofeev

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