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();

    }

}
输出结构

最后输出结果,完全如我们所预积的那样,生产一件商品,消费一件商品,这说明 生产者 线程与 消费者 线程 建立了通信。这个通信本质上时通过一个 全局互斥变量 hasGoods 实现的。

Last updated

Was this helpful?