Java 等待通知机制

解决原生锁上的条件等待问题

满足条件执行问题


如果线程不满足继续执行的条件,不断循环,资源浪费

Sample
1
2
3
while(!condition) {
}
doSomething();

为了更节约,不满足条件,睡眠一段再检测,缺点是依靠间隔不能及时感知,如果缩短睡眠间隔,又会导致开销增加

Sample
1
2
3
4
while(!condition) {
sleep(1000);
}
doSomething();

wait/notify


wait(),notify(),notifyAll()是Object的基本方法,并且都是native的
wait可以无限期等待,实际上是传入过期时间0实现,wait过程中响应中断

Object.java
1
2
3
4
5
6
public final void wait() throws InterruptedException {
wait(0);
}
public final native void wait(long timeout) throws InterruptedException;
public final native void notify();
public final native void notifyAll();

每个对象自身monitor维护一个等待队列

  • 要操控对象的前提是必须先获得锁,因此wait和notify操作都需要在同步块内使用
  • 占有锁的线程无法继续进行,调用wait主动释放锁给别人,线程进入等待状态
  • 多个线程都在等待,调用notify只能唤醒一个,notifyAll全部唤醒
  • 由于之前释放了锁,被唤醒后需要重新获得锁才能继续,实际上notify的作用是从等待队列移动到同步队列
  • 调用notify后,其他线程虽然被唤醒,但是要等到执行notify的线程退出同步块释放锁后才有机会执行
  • 即使没notify线程也可能会被无原因唤醒,wait总要在循环检查内调用,以便错误唤醒时可以保持正确行为

等待逻辑

Sample
1
2
3
4
5
6
7
synchronized(obj) {
obj.doSomething();
while(!condition) {
obj.wait();
}
}
// continue logic

唤醒逻辑

Sample
1
2
3
4
5
synchronized(obj) {
obj.doSomething();
// meet the condition
obj.notifyAll();
}

使用同步方法也可以,默认是以当前实例作为锁

Sample
1
2
3
4
5
6
public synchronized void needWait() throws InterruptedException {
wait();
}
public synchronized void needNotify() {
notifyAll();
}

Wait-Notify为一种偏底层的机制,一般直接使用更高级的类实现等待唤醒逻辑

JDK应用


java.net.InetAddress

多线程通过host查ip,有一个去查就可以,其他等待

InetAddress.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
//维护查询中的host
private static final HashMap<String, Void> lookupTable = new HashMap<>();

private static InetAddress[] checkLookupTable(String host) {
synchronized (lookupTable) {
//插入一条null记录,表明即将开始查询
if (lookupTable.containsKey(host) == false) {
lookupTable.put(host, null);
//返回null, 外围开始查询逻辑
return null;
}

//有记录,说明其他线程在查,需要等待
while (lookupTable.containsKey(host)) {
try {
lookupTable.wait();
} catch (InterruptedException e) {
}
}
}

//其他线程已经查完,去缓存中访问
InetAddress[] addresses = getCachedAddresses(host);
//发现缓存里没有,说明其他线程没查到,插入null记录表示自己线程去查
if (addresses == null) {
synchronized (lookupTable) {
lookupTable.put(host, null);
return null;
}
}
return addresses;
}

查询完毕后调用,清除查询标记,通知其他线程已经查完

InetAddress.java
1
2
3
4
5
6
private static void updateLookupTable(String host) {
synchronized (lookupTable) {
lookupTable.remove(host);
lookupTable.notifyAll();
}
}