在我知道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!
。
注意点:
- 切记 wait 操作和 notify 操作要在同一个临界区中进行。
- 在执行 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;
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?