超时

介于“直接balk并返回”和“等待到守护条件成立为止”这两种极端的处理方法之间,还有一种处理方法,那就是“在守护条件成立之前等待一段时间”。在守护条件成立之前等待一段时间,如果到时条件还未成立,则直接balk。将这种处理称为guarded timed或timeout。

wait何时终止

在调用Java的wait方法时,可以传入参数,以指定超过时间。
线程进入obj的等待队列,停止运行,并释放持有的obj锁。当下列情况发生时,线程便会退出等待队列。

notify方法执行时

即obj的notify方法被执行后,线程被唤醒了的情况。
但当等待队列中有多个等待线程时,只能有一个线程被唤醒。到底唤醒哪一个线程,Java规范中并没有明确规定。

notifyAll方法执行时

即obj的notifyAll方法被执行了的情况。
notifyAll会唤醒实例的等待队列中的所有线程。不管是notify,还是notifyAll,线程被唤醒后,都必须重新获取obj的锁。

interrupt方法执行时

即线程的interrupt方法被执行了的情况。
当被interrupt时,等待队列中的线程(与被notify、notifyAll时一样)会重新获取obj的锁,然后抛出InterruptedException异常。
notify和notifyAll这两个方法是用于调用实例的,而interrupt方法是用于调用线程的。

超时发生时

即wait方法的参数中指定的超时时间到期的情况。
与被notify、notifyAll时一样,这时也要重新获取obj的锁。

guarded timed的实现

使用wait方法来实现guarded timed。超时异常使用java.util.concurrent.TimeoutException。

Host.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.concurrent.TimeoutException;

public class Host {
private final long timeout;
private boolean ready = false;

public Host(long timeout) {
this.timeout = timeout;
}

public synchronized void setExecutable(boolean on) {
ready = on;
notifyAll();
}

public synchronized void execute() throws InterruptedException, TimeoutException {
long start = System.currentTimeMillis();

while (!ready) {
long now = System.currentTimeMillis();
long rest = timeout - (now - start);

if (rest <= 0) {
throw new TimeoutException("now - start = " + (now - start) + ", timeout = " + timeout);
}

wait(rest);
}

doExecute();
}

private void doExecute() {
System.out.println(Thread.currentThread().getName() + " calls doExecute");
}
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.util.concurrent.TimeoutException;

public class Main {

public static void main(String[] args) {
Host host = new Host(10000);

try {
System.out.println("execute BEGIN");
host.execute();
} catch (TimeoutException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

运行结果

1
2
3
4
execute BEGIN
java.util.concurrent.TimeoutException: now - start = 10004, timeout = 10000
at Host.execute(Host.java:24)
at Main.main(Main.java:10)

java.util.concurrent中的超时

java.util.concurrent包中提供了如下两个用于超时处理的方法。

通过异常通知超时

当发生超时抛出异常时,返回值并不适合用于表示超时,需要使用java.util.concurrent.TimeoutException异常。

  1. java.util.concurrent.Future接口的get方法
  2. java.util.concurrent.Exchanger类的exchange方法
  3. java.util.concurrent.Cyclicarrier类的await方法
  4. java.util.concurrent.CountDownLatch类的await方法

通过返回值通知超时

当执行多次try时,则不使用异常,而是使用返回值来表示超时。

  1. java.util.concurrent.BlockingQueue接口
  2. java.util.concurrent.Semaphore类
  3. java.util.concurrent.locks.lock接口