多线程(二) -- 管程(七) -- 生产者消费者模型、虚假唤醒、Condition实现精准通知

1. 什么是生产者/消费者模型

一种重要的模型,基于等待/通知机制。生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点:

  1. 生产者生产的时候消费者不能消费
  2. 消费者消费的时候生产者不能生产
  3. 缓冲区空时消费者不能消费
  4. 缓冲区满时生产者不能生产

1.1 生产者消费者模型的优点:

  1. 解耦。
    因为多了一个缓冲区,所以生产者和消费者并不直接相互调用,这一点很容易想到,这样生产者和消费者的代码发生变化,都不会对对方产生影响,这样其实就把生产者和消费者之间的强耦合解开,变为了生产者和缓冲区/消费者和缓冲区之间的弱耦合
  2. 通过平衡生产者和消费者的处理能力来提高整体处理数据的速度,这是生产者/消费者模型最重要的一个优点。
    如果消费者直接从生产者这里拿数据,如果生产者生产的速度很慢,但消费者消费的速度很快,那消费者就得占用CPU的时间片白白等在那边。有了生产者/消费者模型,生产者和消费者就是两个独立的并发体,生产者把生产出来的数据往缓冲区一丢就好了,不必管消费者;消费者也是,从缓冲区去拿数据就好了,也不必管生产者,缓冲区满了就不生产,缓冲区空了就不消费,使生产者/消费者的处理能力达到一个动态的平衡

2. 利用wait/notify实现:

需求:生产一个、消费一个

/**
 * 生产者消费者模型三段式:
 * 1. 判断等待
 * 2. 执行的业务
 * 3. 通知
 */
public class WithWaitAndNotify {
    public static void main(String[] args) {
        ProdAndCons prodAndCons = new ProdAndCons();
        // 生产线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                prodAndCons.increment();
            }
        }).start();
        // 消费线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                prodAndCons.decrement();
            }
        }).start();
    }
}

class ProdAndCons {
    // 相当于一个缓冲区
    private int number = 0;
	
	//生产者
    public synchronized void increment() {
        // 1.判断等待
        if (number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 2.业务
        number++;
        System.out.println(Thread.currentThread().getName() + "生产了商品,数量:" + number);
        // 3.通知:通知其他线程,我生产了商品
        this.notifyAll();
    }

	// 消费者
    public synchronized void decrement() {
        // 1.判断等待
        if (number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 2.业务
        number--;
        System.out.println(Thread.currentThread().getName() + "消费了商品,数量:" + number);
        // 3.通知:通知其他线程,我消费了商品,请生产
        this.notifyAll();
    }
}

查看结果:

Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0

2.1 问题:

此时我只开了2个线程,假如线程很多呢?
再添加2个或三个生产者消费者线程,这里不展示代码了,直接上结果:

Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-0生产了商品,数量:2
Thread-2生产了商品,数量:3
Thread-0生产了商品,数量:4
Thread-2生产了商品,数量:5
Thread-0生产了商品,数量:6
Thread-2生产了商品,数量:7
Thread-0生产了商品,数量:8
Thread-2生产了商品,数量:9
Thread-3消费了商品,数量:8
Thread-3消费了商品,数量:7
Thread-3消费了商品,数量:6
Thread-3消费了商品,数量:5
Thread-3消费了商品,数量:4
Thread-1消费了商品,数量:3
Thread-1消费了商品,数量:2
Thread-1消费了商品,数量:1
Thread-1消费了商品,数量:0

大家可以看到,结果很惊人,甚至出现了10件商品,按道理,假如已经生产了一个商品后,就应该wait等待了,可是好像并没有wait等待。这是由于发生了虚假唤醒:

2.2 虚假唤醒:

先来解释下上述问题出现的原因,我们通过结果来假设情况:

Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-0生产了商品,数量:2
。。。。
  1. 线程0生产了一个商品,唤醒所有线程,随后再次抢占到资源,进入if判断条件为真,进入等待状态
  2. 线程2抢占到资源,进入判断条件为真,进入到if条件中的等待状态
  3. 线程1抢占到资源消费了一个商品,随机唤醒一个等待的线程,唤醒了线程2
  4. 线程2继续之前刚刚未完成的任务,生产了一个商品,随机唤醒一个等待的线程,唤醒了线程0
  5. 线程0继续执行刚刚未完成的任务,生产了一个商品,此时商品数量为2

上述情况就是虚假唤醒的原因,那么我们如何避免上述情况的发生呢?
我们可以把if改成while,让他进入循环,不停的判断是否已经生产或者已经消费了。查看结果:

Thread-0生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-3消费了商品,数量:0

可以看到,是我们想要的结果。

查看API官方文档:
在这里插入图片描述

3. 利用Lock及codition实现:

代码:

/**
 * 生产者消费者模型三段式:
 * 1. 判断等待
 * 2. 执行的业务
 * 3. 通知
 */
public class WithLockAndCondition {
    public static void main(String[] args) {
        ProdAndCons2 prodAndCons = new ProdAndCons2();
        // 生产线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                prodAndCons.increment();
            }
        }).start();
        // 消费线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                prodAndCons.decrement();
            }
        }).start();
        // 生产线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                prodAndCons.increment();
            }
        }).start();
        // 消费线程
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                prodAndCons.decrement();
            }
        }).start();
    }
}

class ProdAndCons2 {
    // 相当于一个缓冲区
    private int number = 0;
    Lock lock = new ReentrantLock();
    // 获取锁的对象监视器
    Condition condition = lock.newCondition();

    // 生产者
    public void increment() {
        try {
            lock.lock();
            // 1.判断等待
            while (number > 0) {
                condition.await();
            }
            // 2.业务
            number++;
            System.out.println(Thread.currentThread().getName() + "生产了商品,数量:" + number);
            // 3.通知:通知其他线程,我生产了商品
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    // 消费者
    public void decrement() {
        try {
            lock.lock();
            // 1.判断等待
            while (number == 0) {
                condition.await();
            }
            // 2.业务
            number--;
            System.out.println(Thread.currentThread().getName() + "消费了商品,数量:" + number);
            // 3.通知:通知其他线程,我生产了商品
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果:

Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-3消费了商品,数量:0
Thread-0生产了商品,数量:1
Thread-1消费了商品,数量:0
Thread-2生产了商品,数量:1
Thread-3消费了商品,数量:0

3.1 Condition实现精准通知

代码:

public class ConditionChoose {
    public static void main(String[] args) {
        ProdAndCons3 prodAndCons3 = new ProdAndCons3();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                prodAndCons3.printA();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                prodAndCons3.printB();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < 3; i++) {
                prodAndCons3.printC();
            }
        }, "C").start();
    }
}

class ProdAndCons3 {
    private final Lock lock = new ReentrantLock();
    private final Condition condition1 = lock.newCondition();
    private final Condition condition2 = lock.newCondition();
    private final Condition condition3 = lock.newCondition();
    private int number = 1;

    public void printA() {
        try {
            lock.lock();
            // 业务, 判断 -> 执行 -> 通知
            while (number != 1) {
                // 等待
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>AAA");
            // 唤醒指定的人B:
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printB() {
        try {
            lock.lock();
            // 业务, 判断 -> 执行 -> 通知
            while (number != 2) {
                // 等待
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>BBB");
            // 唤醒指定的人C:
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printC() {
        try {
            lock.lock();
            // 业务, 判断 -> 执行 -> 通知
            while (number != 3) {
                // 等待
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + "=>CCC");
            // 唤醒指定的人A:
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

结果:

A=>AAA
B=>BBB
C=>CCC
A=>AAA
B=>BBB
C=>CCC
A=>AAA
B=>BBB
C=>CCC

创建多个conditon对象,每个condition对象控制一个线程的唤醒与等待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值