Java实现生产者与消费者模型

1. wait与notify方法

这两个方法实现线程间同步(通信);

调用wait()和notify()方法都需要先获取到该对象的Monitor锁,即调用这两个方法必须包含在synchronized代码块中;

每一个对象都有两个队列:
在这里插入图片描述

黄色部分正是证明了: 在线程被notify方法唤醒后,并不会马上继续执行这个线程,而是要等到调用notify的那个线程执行结束后释放了对象锁,这个线程才有机会去获取到锁;

2. 单线程版生产与消费者模型

单线程的生产消费者模型:

class Goods {
    private String goodsName;
    private int count;
    public synchronized void set(String goodsName) {
        if(count == 1) {
            System.out.println("此时还有商品,需要等待消费者消费了再继续生产!");
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.goodsName = goodsName;
        count++;
        System.out.println(Thread.currentThread().getName()+"生产:"+ this);
        //唤醒消费者线程
        this.notify();
    }
    public synchronized void get() {
        if(count == 0) {
            System.out.println("此时商品卖完了,需要等待生产者生产!");
            try {
                //this代表Goods对象,就是生产者或者消费者传进来的那个产品对象
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        count--;
        System.out.println(Thread.currentThread().getName() + "消费:"+this);
        //唤醒生产者
        this.notify();
    }

    @Override
    public String toString() {
        return "Goods{" +
                "goodsName='" + goodsName + '\'' +
                ", count=" + count +
                '}';
    }
}
class Producer implements Runnable{
    private Goods goods;

    public Producer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        this.goods.set("macbook");
    }
}

class Consumer implements Runnable {
    private Goods goods;

    public Consumer(Goods goods) {
        this.goods = goods;
    }

    @Override
    public void run() {
        this.goods.get();
    }
}
public class PCTest {
    public static void main(String[] args) throws Exception{
        Goods goods = new Goods();
        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);
        Thread thread = new Thread(producer, "生产者");
        Thread thread1 = new Thread(consumer,"消费者");
        thread1.start();
        Thread.sleep(1000);
        thread.start();
    }
}

3. 完备的生产与消费者模型⭐

首先注意下面几点:

  • 我们的生产者消费者操作的都是同一个队列,即多个线程要同时对队列进行增和删的操作,所以我们需要在每个线程中先获取的这个队列对象的锁,再进行操作,这样就是线程安全的,所以无论何时,多个生产者和多个消费者中有且只有一个在操作商品队列;
  • 唤醒方法用notifyAll()而不能用notify(),在多消费者与多生产者时,如果用notify(),可能会造成假死的状况,也就是所有的生产者与消费者都进入了waiting状态,没有线程再能够将他们唤醒;
    具体原因是notify()只能唤醒一个线程,当连续唤醒的都是同一类型时(即生产者唤醒了生产者,消费者唤醒了消费者),很可能出现假死的情况;

第一步:先来创建一个商品类,这个类很简单,代码如下:

public class Goods {
    private String name;
    private double price;

    public Goods(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

接下来创建生产者: 特别注意下面几点

  • run方法中的wile(true)的目的是让每个生产者可以持续生产,不至于一次生产后就线程结束了;

  • run方法中的那个延时有两个目的,一是为了让每个线程间的竞争更加公平(⭐),二是模拟生产者的生产需要一定时间;

    • 栗子:假如现在队列中有多个商品,假如现在消费者1拿到了商品队列的对象锁,进入了synchronized块中进行了消费,此时结束后又回到了下一次循环,这时没有延时,该线程又立马进入synchronized块进行下一次消费,让其他消费线程无法夺取到对象锁,直到这个线程把所有的商品消费完,然后才调用wait方法,此时其他线程才有机会夺取到锁;
      注意:虽然消费者1执行完一次消费后会释放锁了再进入下一次循环,看起来是和其他线程公平竞争的,但是线程在刚释放锁后更容易马上又抢到锁,这里我们去掉消费者和生产者的延时,运行得到下面结果:

      Producer–0生成商品:Goods{name=‘商品-9698’, price=1.6664665647848553}
      Producer–0生成商品:Goods{name=‘商品-9699’, price=9.463335771991765}
      Producer–0生成商品:Goods{name=‘商品-9700’, price=2.6666139072229678}
      Producer–0生成商品:Goods{name=‘商品-9701’, price=9.840984890797923}
      Producer–0生成商品:Goods{name=‘商品-9702’, price=3.7271200220546374}
      Producer–0 商品队列已满…等待。。。。
      Consumer–3获取到商品 :Goods{name=‘商品-9695’, price=1.2373569995397937}
      Consumer–3获取到商品 :Goods{name=‘商品-9696’, price=6.987477844596761}
      Consumer–3获取到商品 :Goods{name=‘商品-9697’, price=1.5927845982781375}
      Consumer–3获取到商品 :Goods{name=‘商品-9698’, price=1.6664665647848553}
      Consumer–3获取到商品 :Goods{name=‘商品-9699’, price=9.463335771991765}
      Consumer–3获取到商品 :Goods{name=‘商品-9700’, price=2.6666139072229678}
      Consumer–3获取到商品 :Goods{name=‘商品-9701’, price=9.840984890797923}
      Consumer–3获取到商品 :Goods{name=‘商品-9702’, price=3.7271200220546374}
      Consumer–3 商品队列已空,通知生产
      Consumer–4 商品队列已空,通知生产
      Consumer–0 商品队列已空,通知生产
      Consumer–2 商品队列已空,通知生产
      Consumer–1 商品队列已空,通知生产
      Producer–1生成商品:Goods{name=‘商品-9703’, price=9.886119511951076}
      Producer–1生成商品:Goods{name=‘商品-9704’, price=2.2185389445487824}
      Producer–1生成商品:Goods{name=‘商品-9705’, price=8.570631825557799}
      Producer–1生成商品:Goods{name=‘商品-9706’, price=3.974329606563911}

      可以看到,去掉延时后,生产与消费总是谁先夺取到锁,就会一直消费或者生产直到消费到0或者生产满队列;

  • while (this.goodsQueue.size() >= this.maxGoods)这一句中的wile不能改为if哟,如果是if,当商品为0时,此时多个生产者wait在这,当随便一个消费者或其他生产者调用notiyfAll()后,这几个wait的生产者都会继续执行下面的生产代码,并没有再次判断商品总量是否超标,所以会导致商品数量超过我们设定的值;
    对应的消费者中如果改成if,则可能会出现消费过度,即没有了还在消费,将会造成队列为空的时候还在出队;

public class Producer implements Runnable{
    private final Queue<Goods> goodsQueue;
    private final Integer maxGoods;
    private final long speed;
    //goodID用来给产品编号
    private final AtomicInteger goodsID = new AtomicInteger(0);

    public Producer(Queue<Goods> goodsQueue, Integer maxGoods, long speed) {
        this.goodsQueue = goodsQueue;
        this.maxGoods = maxGoods;
        this.speed = speed;
    }

    @Override
    public void run() {
        while(true) {
            //这个延时一定得放在这,不能放在synchronized块中
            try {
                Thread.sleep(speed);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this.goodsQueue){
                while (this.goodsQueue.size() >= this.maxGoods) {
                    try {
                        System.out.println(Thread.currentThread().getName()+" 商品队列已满...等待。。。。");
                        this.goodsQueue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Goods goods = new Goods("商品-"+goodsID.getAndIncrement(),Math.random()*10);
                this.goodsQueue.add(goods);
                System.out.println(Thread.currentThread().getName()+"生成商品:"+goods.toString());
                this.goodsQueue.notifyAll();

            }
        }
    }
}

消费者与生产者差不多,代码如下:

public class Consumer implements Runnable{
    private final Queue<Goods> goodsQueue;
    private final long speed;

    public Consumer(Queue<Goods> goodsQueue, long speed) {
        this.goodsQueue = goodsQueue;
        this.speed = speed;
    }

    @Override
    public void run() {
        while(true) {
            try {
                Thread.sleep(speed);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this.goodsQueue) {
                while(this.goodsQueue.isEmpty()) {
                    try {
                        System.out.println(Thread.currentThread().getName()+" 商品队列已空,通知生产");
                        this.goodsQueue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                Goods goods = this.goodsQueue.poll(); //检索并删除此队列的头,如果此队列为空,则返回 null 。
                if(goods != null) {
                    System.out.println(Thread.currentThread().getName()+"获取到商品 :"+goods);
                }
                this.goodsQueue.notifyAll();
            }
        }
    }
}

接下来我们创建一个管理类,用来管理生产与消费者:

public class Manager {
    final long Pspeed;
    final long Cspeed;
    final int numOfProducer;
    final int numOfComsumer;
    final int maxGoods;
    final Queue<Goods> goodsQueue = new LinkedList<>();
    final Producer producer;
    final Consumer consumer;

    public Manager(Properties properties) {
        Pspeed = Integer.parseInt(properties.getProperty("producer.speed"));
        Cspeed = Integer.parseInt(properties.getProperty("consumer.speed"));
        numOfProducer = Integer.parseInt(properties.getProperty("producer.num"));
        numOfComsumer = Integer.parseInt(properties.getProperty("consumer.num"));
        maxGoods = Integer.parseInt(properties.getProperty("good.maxGoods"));
        producer = new Producer(goodsQueue,maxGoods,Pspeed);
        consumer = new Consumer(goodsQueue,Cspeed);
    }
    public void start() {
        for(int i=0; i<numOfProducer; i++) {
            new Thread(producer,"Producer--"+i).
            start();
        }
        for(int i=0; i<numOfComsumer; i++) {
            new Thread(consumer,"Consumer--"+i).
            start();
        }
    }
    //打印配置信息
    public String getCurrentInfo() {
        StringBuilder sb = new StringBuilder();
        sb.append("-- 当前生产者消费者模型中的配置信息如下 --");
        sb.append("\n");
        sb.append("容量:");
        sb.append(this.maxGoods);
        sb.append("\n");
        sb.append("生产者数量:");
        sb.append(this.numOfProducer);
        sb.append("\n");
        sb.append("生产速率(ms):");
        sb.append(this.Pspeed);
        sb.append("\n");
        sb.append("消费者数量:");
        sb.append(this.numOfComsumer);
        sb.append("\n");
        sb.append("消费速率(ms):");
        sb.append(this.Cspeed);
        sb.append("\n");
        return sb.toString();
    }
}

上面管理类我们需要创建一个对应的属性文件,装载着我们的配置信息:

#不能有分号,否则报错
producer.num = 3
producer.speed = 1500
consumer.num = 5
consumer.speed = 2500
good.maxGoods = 8

最后建立测试类:

public class TestProducerConsumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        try(InputStream inputStream = TestProducerConsumer
                .class.getClassLoader().getResourceAsStream("com/bittech/com/bittech/pc/hello.properties")) {
            properties.load(inputStream);
        }catch (IOException e) {
            e.printStackTrace();
        }

        Manager manager = new Manager(properties);
        //打印属性文件中设置的参数
        System.out.println(manager.getCurrentInfo());
        manager.start();
    }
}

进行测试,结果如下:

– 当前生产者消费者模型中的配置信息如下 –
容量:8
生产者数量:3
生产速率(ms):1500
消费者数量:5
消费速率(ms):2500

Producer–0生成商品:Goods{name=‘商品-0’, price=3.298510421377341}
Producer–1生成商品:Goods{name=‘商品-1’, price=4.406656146706069}
Producer–2生成商品:Goods{name=‘商品-2’, price=0.6762076601105449}
Consumer–1获取到商品 :Goods{name=‘商品-0’, price=3.298510421377341}
Consumer–0获取到商品 :Goods{name=‘商品-1’, price=4.406656146706069}
Consumer–3获取到商品 :Goods{name=‘商品-2’, price=0.6762076601105449}
Consumer–2 商品队列已空,通知生产
Consumer–4 商品队列已空,通知生产
Producer–0生成商品:Goods{name=‘商品-3’, price=4.97265347079696}
Consumer–4获取到商品 :Goods{name=‘商品-3’, price=4.97265347079696}
Consumer–2 商品队列已空,通知生产
Producer–1生成商品:Goods{name=‘商品-4’, price=8.870488585259725}
Producer–2生成商品:Goods{name=‘商品-5’, price=2.179215869410979}
Consumer–2获取到商品 :Goods{name=‘商品-4’, price=8.870488585259725}
Producer–2生成商品:Goods{name=‘商品-6’, price=0.7527747030954623}
Producer–0生成商品:Goods{name=‘商品-7’, price=0.1380726602635174}
Producer–1生成商品:Goods{name=‘商品-8’, price=0.739801987555937}
Consumer–1获取到商品 :Goods{name=‘商品-5’, price=2.179215869410979}
Consumer–0获取到商品 :Goods{name=‘商品-6’, price=0.7527747030954623}
Consumer–3获取到商品 :Goods{name=‘商品-7’, price=0.1380726602635174}
Consumer–4获取到商品 :Goods{name=‘商品-8’, price=0.739801987555937}
Consumer–2 商品队列已空,通知生产
Producer–2生成商品:Goods{name=‘商品-9’, price=3.684812679654467}
Consumer–2获取到商品 :Goods{name=‘商品-9’, price=3.684812679654467}
Producer–1生成商品:Goods{name=‘商品-10’, price=7.4808216795036655}
Producer–0生成商品:Goods{name=‘商品-11’, price=9.434210896373118}
Consumer–1获取到商品 :Goods{name=‘商品-10’, price=7.4808216795036655}
Consumer–3获取到商品 :Goods{name=‘商品-11’, price=9.434210896373118}
Consumer–0 商品队列已空,通知生产
Producer–1生成商品:Goods{name=‘商品-12’, price=1.4348959559594199}
Consumer–0获取到商品 :Goods{name=‘商品-12’, price=1.4348959559594199}
Producer–0生成商品:Goods{name=‘商品-13’, price=8.631376618366412}
Producer–2生成商品:Goods{name=‘商品-14’, price=5.5106825887534105}
Consumer–4获取到商品 :Goods{name=‘商品-13’, price=8.631376618366412}

Process finished with exit code -1

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值