wait notifyAll 线程间通信
用生产者 - 消费者模型讲清 线程间通信。也讲清 wait 方法 和 notifyAll 方法的使用场景。
生产者 - 消费者 模型
假设我们有一个场景:
生产者向传送带上放入一件商品,消费者从传送带上取出一件商品。
简单来说,就是生产一件商品,消费一件商品。

我们用两个线程来表示 生产者 和 消费者。比如 生产者 看到传送带上 有一件商品,就原地等待。消费者看到传送带 上有商品 就取一件商品,然后传送带上没有商品了,消费者也原地等待。生产者,消费者互相等待,造成死锁。
为了解决死锁,很显然 两个线程需要相互通信。我们要对两个 线程 的功能略作修改:
生产者负责将产品并且放入传送带,一次放一件商品,同时提醒消费者从传送带取商品。当传送带上有商品时, 提醒消费者取出商品,生产者 等待 消费者。
消费者负责从传送带上取出商品,一次取一件商品,取出后提醒生产者在传送带上放入商品。
若传送带上没有商品,提醒生产者在传送带上放入商品,消费者等待 生产者。
wait() 方法 与 notifyAll() 方法
使线程进入等待状态,可以调用 线程 wait() 函数实现。
唤醒其他线程进入就绪状态, 可以调用notify() 或者 notifyAll() 方法实现。两者的区别是,notify()唤醒哪一个线程,有操作系统决定,notifyAll()一次性唤醒所有等待状态的线程。实际开发中推荐使用 notifyAll() 函数,因为 notify() 函数可能没有唤醒 我们想要唤醒的线程。
为了让两个线程互相知道什么时候应该进入等待状态, 什么时候需要唤醒另外一个线程,我们需要设置一个全局的互斥量。这个互斥量用于表示传送带上是否有商品,若有商品,生产者等待,唤醒消费者。若无商品,消费者等待,唤醒生产者。
我们编写一个 GoodsPipe 类, 用来模拟商品传送带。 GoodsPipe 有两个函数 send 和 pick, 分别表示向传送带上分入一个商品,和从传送带上取下一件商品。我们还设置了 一个变量 n 用于表示传送带上商品数量,一个布尔型变量 hasGood 用于表示传送带上是否还有商品。我们对 send 函数 和 pick 函数 都用 synchronization 进行修饰,表示任意线程调用这个方法,其他线程都不允许抢占。
public class GoodsPipe {
private int n;//商品数量
boolean hasGood;
public GoodsPipe(){
n=0;
hasGood=false;
}
//向传送带上放一个商品
//我们希望放入产品时,不会中途取出一个产品,导致产品数量出错
public synchronized void send() {
if (hasGood){//当传送带上有商品时,不需要生产,等待消费
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
n++;
hasGood=true;
System.out.println("生产 一个商品,还剩"+n+"个商品");
notifyAll();//唤醒消费者
}
}
//从传送带上去一个商品
//我们希望取出产品时,不会中途放入一个产品,导致产品数量出错
public synchronized void pick() {
if (! hasGood){//如果没有商品,无法消费,等待生产
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
n--;
hasGood=false;
System.out.println("消费 一个商品,还剩"+n+"个商品");
notifyAll();//唤醒生产者
}
}
}
我们编写了Produce 类, 用于传递给 Thread 构造函数 创建 生产者 线程。
//用于创建 生产者 线程
public class Producer implements Runnable{
private GoodsPipe goodsPipe;
public Producer(GoodsPipe goodsPipe) {
this.goodsPipe = goodsPipe;
}
//模拟生产商品
@Override
public void run() {
//不停地向传送带上送商品
while(true){
goodsPipe.send();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
我们还编写了 Consumer 类, 用于传递给 Thread 构造函数 创建 消费者 线程。
public class Consumer implements Runnable{
private GoodsPipe goodsPipe;
public Consumer(GoodsPipe goodsPipe) {
this.goodsPipe = goodsPipe;
}
//模拟消费产品
@Override
public void run() {
while (true){
goodsPipe.pick();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
最后我们还编写了一个测试类,用于测试 生产者 - 消费者 模型。
public class Test {
public static void main(String[] args) {
GoodsPipe pipe = new GoodsPipe();
//生产者, 消费者 线程
Thread p=new Thread(new Producer(pipe));
Thread c=new Thread(new Consumer(pipe));
//运行
p.start();
c.start();
}
}
部分运行结果:
生产 一个商品,还剩1个商品
消费 一个商品,还剩0个商品
生产 一个商品,还剩1个商品
消费 一个商品,还剩0个商品
生产 一个商品,还剩1个商品
消费 一个商品,还剩0个商品
生产 一个商品,还剩1个商品
消费 一个商品,还剩0个商品
生产 一个商品,还剩1个商品
消费 一个商品,还剩0个商品
生产 一个商品,还剩1个商品
消费 一个商品,还剩0个商品
最后输出结果,完全如我们所预积的那样,生产一件商品,消费一件商品,这说明 生产者 线程与 消费者 线程 建立了通信。这个通信本质上时通过一个 全局互斥变量 hasGoods 实现的。
Last updated