Java 中 wait 与 notify 的简单操作

在我知道wait() 与 notify() 以前,我常常用一种看似很 low 的方式控制线程同步

1
2
3
while(condition) {
return ;
}

实际上这个线程是一直在运行的,并没有操作系统概念中的阻塞(Block)。而要实现阻塞(Block)则要借助 Java 线程中的 wait() 操作 与 notify() 操作。

相对简单的例子

wait() 操作与 notify() 操作必须在临界区内进行。而 synchronized 需要一个对象用作锁,以区分各个不同的临界区。比如临界区 A 中进行了 wait() 操作,在也必须在临界区A 中进行 notify() 操作。

现看一个例子:

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
public class Test {
public static void main(String[] args) {
TestThread testThread = new TestThread();
testThread.start();
synchronized (testThread) {
System.out.println("Before wait");
try {
testThread.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("All completed!");
}
}

class TestThread extends Thread {
@Override
public void run() {
synchronized (this) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("notify() Completed");
notify();
}
}
}

例子中用了两次 synchronized ,但两次对应的参数是相同的,所以这两块代码都属于同一个临界区。当执行 wait 操作时,程序进入阻塞状态,在进行 notify 操作前,不会进行下一步,即不会输出All completed!。当 sleep 完成,即过了 5 秒钟后,触发 notify() 操作,打印notify() Completed。此时程序从阻塞状态进入就绪状态,然后进入运行状态,输出All completed!

注意点:

  1. 切记 wait 操作和 notify 操作要在同一个临界区中进行。
  2. 在执行 wait 操作时会抛出一个 InterruptedException 的异常,记得捕获。

输出结果参考:

1
2
3
Before wait
notify() Completed
All completed!

相对复杂的例子

从上面一个例子看出,wait 和 notify 操作实现了进程同步,类似于播放器的暂停(wait)与继续(notify)。只有在 notify 的情况下才能继续 wait 之后的内容,可以保证一些临界值的准确性。

在操作系统概念中,有一个典型的 消费者与生产者 的模型。在一块区域中,最多存放 5 个 unit 的物品,当区域中的物品少于 5 个时,生产者就会生产 1 个 unit 的物品放在区域内;当区域中的物品大于 0 个时,消费者就会从区域中取走 1 个 unit 的物品。

如果用 wait 与 notify 模拟的话,那就是要控制好两者的出现时机。

先看代码:

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import java.util.Date;
import java.util.Vector;
/**
* Created by lazzzis on 2/9/16.
*/
public class Test{
public static void main(String[] args) {
Producer producer = new Producer();
producer.start();
new Consumer(producer).start();
try {
Thread.sleep(80);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.exit(0);
}
}
class Producer extends Thread {
static final int MAXQUEUE = 5;
private Vector messages = new Vector();
@Override
public void run() {
try {
while (true) {
this.putMessage();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private synchronized void putMessage() throws InterruptedException {
while (messages.size() == MAXQUEUE) {
System.out.println("I\'m waiting!");
wait();
}
messages.addElement(new Date().toString());
System.out.println("put message");
notify();
}
public synchronized String getMessage() throws InterruptedException {
notify();
while (messages.size() == 0) {
wait();
}
String message = (String) messages.firstElement();
messages.removeElement(message);
return message;
}
}
class Consumer extends Thread {
Producer producer;
public Consumer(Producer p) {
producer = p;
}
@Override
public void run() {
try {
while (true) {
String message = producer.getMessage();
System.out.println("Got message: " + message);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

注意点:

  1. synchronized对于同一对象的不同方法,算作同样的临界区
  2. 两个方法中 notify 和 wait 的顺序相反,如果相同,极有可能发生两者同时处于wait状态,而导致无法触发 notify 的情况。
  3. 在 getMessage 中触发 notify 会使触发 wait 的 putMessage 继续运行,相当于一旦有物品被取走,就会通知生产者马上生产一个
  4. 在 putMessage 中触发 notify 会使触发 wait 的 getMessage 继续运行,相当于一旦有物品被生产,就会通知消费者马上取走一个。

参考

  1. Java Thread: notify() and wait() examples
  2. How to use wait and notify in Java?