在我知道wait() 与 notify() 以前,我常常用一种看似很 low 的方式控制线程同步
| 12
 3
 
 | while(condition) {return ;
 }
 
 | 
实际上这个线程是一直在运行的,并没有操作系统概念中的阻塞(Block)。而要实现阻塞(Block)则要借助 Java 线程中的 wait() 操作 与 notify() 操作。
相对简单的例子
wait() 操作与 notify() 操作必须在临界区内进行。而 synchronized 需要一个对象用作锁,以区分各个不同的临界区。比如临界区 A 中进行了 wait() 操作,在也必须在临界区A 中进行 notify() 操作。
现看一个例子:
| 12
 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!。
注意点:
- 切记 wait 操作和 notify 操作要在同一个临界区中进行。
- 在执行 wait 操作时会抛出一个 InterruptedException 的异常,记得捕获。
输出结果参考:
| 12
 3
 
 | Before waitnotify() Completed
 All completed!
 
 | 
相对复杂的例子
从上面一个例子看出,wait 和 notify 操作实现了进程同步,类似于播放器的暂停(wait)与继续(notify)。只有在 notify 的情况下才能继续 wait 之后的内容,可以保证一些临界值的准确性。
在操作系统概念中,有一个典型的 消费者与生产者 的模型。在一块区域中,最多存放 5 个 unit 的物品,当区域中的物品少于 5 个时,生产者就会生产 1 个 unit 的物品放在区域内;当区域中的物品大于 0 个时,消费者就会从区域中取走 1 个 unit 的物品。
如果用 wait 与 notify 模拟的话,那就是要控制好两者的出现时机。
先看代码:
| 12
 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;
 
 
 
 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();
 }
 }
 }
 
 | 
注意点:
- synchronized对于同一对象的不同方法,算作同样的临界区
- 两个方法中 notify 和 wait 的顺序相反,如果相同,极有可能发生两者同时处于wait状态,而导致无法触发 notify 的情况。
- 在 getMessage 中触发 notify 会使触发 wait 的 putMessage 继续运行,相当于一旦有物品被取走,就会通知生产者马上生产一个
- 在 putMessage 中触发 notify 会使触发 wait 的 getMessage 继续运行,相当于一旦有物品被生产,就会通知消费者马上取走一个。
参考
- Java Thread: notify() and wait() examples
- How to use wait and notify in Java?