初学者第66节生产者消费者(八)

2019-08-20 15:40:44 浏览数 (3)

前言

上一节讲解了生产者与消费者模式的基本理论以及简单实现,并且遗留下来一个消费商品是null,库存为:-1的问题 ,看下代码。

代码语言:javascript复制
/**
* 首先需要一个商品共享类
*/
Goods goods = new Goods();
Thread producer = new Thread(new Producer(goods));
Thread consumer = new Thread(new Consumer(goods));
//开启消费者线程
consumer.start();
//开始生产者线程
producer.start();

打印的结果为:
消费商品名称为:null,库存为:-1
生产商品名称为:中华香烟一条,库存为:0

上节问题分析

上面问题怎么出现的呢?

1. 先来分析一下第一条打印的信息。因为消费者线程先执行了(this.goods.get();)方法,这个方法里面主要是做了减掉了库存,在还没有增加库存之前就先减掉了库存,并且在减掉的时候还没有做控制,所以这个时候就就打印了消费商品名称为:null,库存为:-1。

2. 分析第二条打印信息。这里是因为在执行生成方法时,库存已经变为-1了,所以在加1之后就会出现第二条打印信息:生产商品名称为:中华香烟一条,库存为:0

改造生产者方法

首先需要在生产方法里面判断库存是否已经有剩余的商品了,如果有的话将等待消费者线程通知,这个判断里面就可以使用wait方法。如果已经没有库存了这个时候就需要生产商品了,生产完毕之后就可以通知消费者线程了,这就需要使用notify方法。

代码语言:javascript复制
/**
 * 生产方法
 * @param goodsName
 */
public synchronized void set(String goodsName) throws InterruptedException {
    //判断库存大于0表示消费者还未消费,所以需要wait等待
    if (this.count > 0) {
        //这里等待消费者的通知
        wait();
    }
    //这里就是开始生产商品了,每次生产一个商品
    this.goodsName = goodsName;
    this.count = this.count   1;
    Thread.sleep(1000);
    System.out.println("生产"   this.toString());
    //这里生产完毕之后通知消费者线程可以继续消费商品了
    notify();
}

改造消费者方法

首先需要在生产方法里面判断库存是否已经没有剩余的商品了,如果库存已经为0了的话,将等待生产者线程通知,这个判断里面就可以使用wait方法。如果还有库存的话,这个时候就可以消费了,消费完毕之后就可以通知生产者了,这就需要使用notify方法。

代码语言:javascript复制
/**
 * 消费方法
 */
public synchronized void get() throws InterruptedException {
    //如果商品库存已经没有商品了就需要等待生产者通知了
    if (this.count == 0) {
        wait();
    }
    //消费商品,每次消费一个商品
    this.count = this.count - 1;
    Thread.sleep(1000);
    System.out.println("消费" this.toString());
    //消费完毕之后需要通知生产者线程可以继续生产商品了
    notify();
}

测试方法

测试方法不变直接运行即可。

代码语言:javascript复制
Goods goods = new Goods();
Thread producer = new Thread(new Producer(goods));
Thread consumer = new Thread(new Consumer(goods));
//开启消费者线程
consumer.start();
//开始生产者线程
producer.start();

结果:
生产商品名称为:中华烟一条,库存为:1
消费商品名称为:中华烟一条,库存为:0

这就没有问题了。

多生产与多消费

以上只有一个生产者生产一次商品和一个消费者只消费一次就结束了,现在能否改变一下,多个生产者和多个消费者呢?这样的话我们该怎么改造代码呢?

分析一下,首先notify方法目前是只能唤醒一个线程,如果有多个生产者线程和多个消费者线程的话,这个notify方法唤醒的线程如果是消费者的话应该没有问题,但是如果是唤醒的也是生产者的线程那么程序就会变成假死状态了,这个时候显然这个notify方法不行,我们上一节讲过了有一个notify唤醒当前对象的所有线程。这个时候就可以使用该方法了,好了我们开始改造。

改造多生产方法

如果想要将一生产改成多生产的情况下需要修改2个地方。

1. 就需要将if修改为while,因为要每次被唤醒生产线程之后,有可能还有库存,所以就不需要生产,然后继续等待被唤醒即可。修改goods类的消费方法。看代码。

代码语言:javascript复制
/**
 * 生产方法
 * @param goodsName
 */
public synchronized void set(String goodsName) throws InterruptedException {
    /**
     * 判断库存大于0表示消费者还未消费,所以需要wait等待
     * 如果想要多生产的情况下就需要将if修改为while
     * 因为要每次被唤醒之后的线程有可能是库存还有的所以就不需要生产
     */
    while (this.count > 0) {
        //这里等待消费者的通知
        wait();
    }
    //这里就是开始生产商品了,每次生产一个商品
    this.goodsName = goodsName;
    this.count = this.count   1;
    Thread.sleep(1000);
    System.out.println("生产"   this.toString());
    //这里生产完毕之后通知消费者线程可以继续消费商品了
    notify();
}

2. 如果已经生产完毕之后就会唤醒消费者,消费者消费之后程序就运行完了,如果想继续生产那么就需要在生产方法外面在套一个while的死循环。

改造以上2点就可以变成多生产了。修改Producer类的run方法,看代码。

代码语言:javascript复制
@Override
public void run() {
    //变为多生产
    while (true) {
        //这里直接生产“中华香烟一条”
        try {
            this.goods.set("中华烟一条");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

改造多消费方法

如果想要将一消费改成多消费的情况下需要修改2个地方。和上面改造多生产方式类似,基本上一模一样。这里就不写了,直接上代码了。

代码语言:javascript复制
/**
 * 消费方法
 */
public synchronized void get() throws InterruptedException {
    /**
     * 如果商品库存已经没有商品了就需要等待生产者通知了
     * 如果想要多消费的情况下就需要将if修改为while
     * 因为要每次被唤醒之后的线程有可能是库存已经没有商品了也就需要等待生产者通知了
     */
    while (this.count == 0) {
        wait();
    }
    //消费商品,每次消费一个商品
    this.count = this.count - 1;
    Thread.sleep(1000);
    System.out.println("消费" this.toString());
   //为了演示的时候能有层次感,这里加个打印
   System.out.println("===========================");
    //消费完毕之后需要通知生产者线程可以继续生产商品了
    notify();
}

Consumer的run方法修改

代码语言:javascript复制
@Override
public void run() {
    while (true) {
        try {
            //这里消費“中华香烟一条”
            this.goods.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

这下就改造完毕了,可以写测试类了

修改测试类

我们创建10个生产者线程和5个消费者线程试试看。为了显示效果好一点,先在Goods的tostring方法中添加一个获取当前线程名称吧!!!

代码语言:javascript复制
/**
 * 显示效果
 * @return
 */
@Override
public String toString() {
    return "线程名称为:" Thread.currentThread().getName() ",商品名称为:"   goodsName   ",库存为:"   count;
}

看看测试代码

代码语言:javascript复制
public class TestGoods {
    public static void main(String[] args) {
        Goods goods = new Goods();
        //储存生产者与消费者线程的集合
        List<Thread> threadList = new ArrayList<>();
        //添加10个生产线程
        for (int i = 0; i < 10 ; i  ) {
            Thread temp = new Thread(new Producer(goods));
            temp.setName("生产线程"   (i 1));
            threadList.add(temp);
        }
        //添加5个消费线程
        for (int i = 0; i < 5 ; i  ) {
            Thread temp = new Thread(new Consumer(goods));
            temp.setName("消费线程"   (i 1));
            threadList.add(temp);
        }
        //线程开启
        for (int i = 0; i < threadList.size(); i  ) {
            threadList.get(i).start();
        }
    }
}

再来看看结果:

代码语言:javascript复制
生产线程名称为:生产线程1,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程5,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程2,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程1,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程1,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程5,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程10,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程2,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程1,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程5,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程2,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程2,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程1,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程5,商品名称为:中华烟一条,库存为:0
========================================
生产线程名称为:生产线程10,商品名称为:中华烟一条,库存为:1
消费线程名称为:消费线程2,商品名称为:中华烟一条,库存为:0
========================================

这样就是多生产和多消费了。

1 人点赞