Java wait()、notify()和notifyAll()实现线程间通信(附带实例)
多个并发执行的线程,如果它们只是竞争资源,可以采取 synchronized 设置同步代码块来实现对共享资源的互斥访问;如果多个线程在执行的过程中有次序上的关系,那么多个线程之间必须进行通信、相互协调。
例如,经典的生产者和消费者问题。生产者和消费者共享存放产品的仓库,如果仓库为空时,消费者无法消费产品,当仓库装满时,生产者会因产品没有空间存放而无法继续生产产品。
Java 提供 3 个方法来实现线程间的通信问题,分别是 wait()、notify() 和 notifyAll()。
这三个方法只能在 synchronized 关键字起作用的范围内使用,并且是在同一个同步问题中搭配使用这三个方法时才有实际意义:
notify() 和 notifyAll() 方法都是把某个对象上等待队列内的线程唤醒,notify() 方法只能唤醒一个线程,但究竟是哪一个不能确定,而 notifyAll() 方法则是唤醒这个对象上的等待队列中的所有线程。为了安全感,大多数时候应该使用 notifiAll() 方法,除非明确知道需要唤醒是哪一个具体线程。
【实例】线程间通信示例。功能实现:下面的程序模拟了生产者和消费者的关系,生产者在一个循环中不断生产A~G的共享数据,而消费者则不断地消费生产者生产的 A~G 的共享数据。在这一对关系中,必须先有生产者生产,才能有消费者消费。
为了解决这一问题,引入了如下的等待/通知(wait()/notify())机制下:
最开始设置通知变量的值为 true,表示还未生产。此时如果消费者需要消费,则需要修改通知变量并调用 notify() 发出通知。生产者得到通知,开始生产产品,然后修改通知变量,向消费者发出通知。
如果生产者想要继续生产,但因为检测到通知变量为 false,得知消费者还没有消费,所以调用 wait() 进入等待状态。因此,最后的结果是生产者每生产一个就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。
例如,经典的生产者和消费者问题。生产者和消费者共享存放产品的仓库,如果仓库为空时,消费者无法消费产品,当仓库装满时,生产者会因产品没有空间存放而无法继续生产产品。
Java 提供 3 个方法来实现线程间的通信问题,分别是 wait()、notify() 和 notifyAll()。
这三个方法只能在 synchronized 关键字起作用的范围内使用,并且是在同一个同步问题中搭配使用这三个方法时才有实际意义:
- 调用 wait() 方法可以使调用该方法的线程释放共享资源的锁,从可运行状态进入等待状态,直到被再次唤醒;
- 调用 notify() 方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行状态;
- 调用 notifyAll() 方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,优先级最高的那个线程最先执行。
notify() 和 notifyAll() 方法都是把某个对象上等待队列内的线程唤醒,notify() 方法只能唤醒一个线程,但究竟是哪一个不能确定,而 notifyAll() 方法则是唤醒这个对象上的等待队列中的所有线程。为了安全感,大多数时候应该使用 notifiAll() 方法,除非明确知道需要唤醒是哪一个具体线程。
【实例】线程间通信示例。功能实现:下面的程序模拟了生产者和消费者的关系,生产者在一个循环中不断生产A~G的共享数据,而消费者则不断地消费生产者生产的 A~G 的共享数据。在这一对关系中,必须先有生产者生产,才能有消费者消费。
为了解决这一问题,引入了如下的等待/通知(wait()/notify())机制下:
- 在生产者没有生产之前,通知消费者等待;
- 在生产者生产之后,马上通知消费者消费;
- 在消费者消费完之后,通知生产者已经消费完,需要生产。
class ShareStore { private char c; private boolean writeable = true; public synchronized void setShareChar(char c) { if (!writeable) { try { wait(); // 未消费等待 } catch (InterruptedException e) { } } this.c = c; writeable = false; // 标记已经生产 notify(); // 通知消费者已经生产,可以消费 } public synchronized char getShareChar() { if (writeable) { try { wait(); // 未生产等待 } catch (InterruptedException e) { } } writeable = true; // 标记已经消费 notify(); // 通知需要生产 return this.c; } } // 生产者线程 class Producer extends Thread { private ShareStore s; Producer(ShareStore s) { this.s = s; } public void run() { for (char ch = 'A'; ch <= 'G'; ch++) { s.setShareChar(ch); // 生产一个新产品 System.out.println(ch + " 被生产"); } } } // 消费者线程 class Consumer extends Thread { private ShareStore s; Consumer(ShareStore s) { this.s = s; } public void run() { char ch; do { ch = s.getShareChar(); // 消费一个新产品 System.out.println(ch + " 被消费"); } while (ch != 'G'); } } public class ProducerConsumer { public static void main(String argv[]) { ShareStore s = new ShareStore(); // 实例化一个 ShareStore 对象 new Producer(s).start(); // 创建生产者线程并启动 new Consumer(s).start(); // 创建消费者线程并启动 } }上述程序执行结果为:
A 被生产
A 被消费
B 被生产
B 被消费
C 被生产
C 被消费
D 被生产
D 被消费
E 被生产
E 被消费
F 被生产
F 被消费
G 被生产
G 被消费
最开始设置通知变量的值为 true,表示还未生产。此时如果消费者需要消费,则需要修改通知变量并调用 notify() 发出通知。生产者得到通知,开始生产产品,然后修改通知变量,向消费者发出通知。
如果生产者想要继续生产,但因为检测到通知变量为 false,得知消费者还没有消费,所以调用 wait() 进入等待状态。因此,最后的结果是生产者每生产一个就通知消费者消费一个;消费者每消费一个,就通知生产者生产一个,所以不会出现未生产就消费或生产过剩的情况。