线程入门(二)---最简单的消费者和生产者问题

一个进程可以有多个线程,多个线程并发会存在数据安全问题,对于解决多线程的安全问题,有两种方法:
1、同步代码块
synchronized(obj){具体执行的代码块}
其中obj为共享资源、共享对象(obj需要是Object的子类),此时的obj成为同步监视器
在这里插入图片描述

2、同步方法
将核心的代码逻辑定义为一个方法,使用synchronized关键字进行修饰,此时不需要指定共享对象。

同步方法中不需要同步监视器,因为同步方法的监视器是this,也就是该对象本身。
在这里插入图片描述

最简单的消费者与生产者问题

我们来试一下最简单的消费者和生产者的问题解决,先描述一下问题:

生产者可以生产旺仔品牌的小馒头和娃哈哈品牌的矿泉水,生产了便放到商品区(一次只能放一个商品),消费者从商品区取走商品。

此时我们的共享对象是商品区Goods类
在这里插入图片描述

我们先来看一下商品Goods类,商品有属性品牌brand和名称name,而且商品区只有一个位置,我们需要用flag来标识一下。

在这里插入图片描述

然后我们看一下生产者,生产者生产商品,商品的品牌和名称不能弄混,要等到商品区空了才可以放进去(即flag=false;)生产完了flag=true。

因此,flag=true时,生产者线程进入阻塞状态;flag=false时,生产者生产商品(set操作),商品生产完了之后,需要把flag设为true,并唤醒消费者去进行消费。

关于代码,我们此时用的是同步方法实现商品同步,生产者生产了商品,消费者才可以取,否则消费者不可以取商品,set和get都是原子操作

在这里插入图片描述
接着我们看一下消费者,如果flag等于false的话,意味着生产者没有生产商品,此时消费者无法消费,需要让消费者线程进入到阻塞状态;在商品区有商品时(即flag=true;),消费者可以消费商品,消费完flag就为false,最后唤醒生产者去进行生产。

生产者也是需要在消费者消费了才可以去set商品,因此set和get都是原子操作

在这里插入图片描述
最后,我们将代码整理好,需要写Goods、Consumer、Producer三个类。

先看看Goods类:

public class Goods {

    private String brand;
    private String name;
    //默认是不存在商品的,如果值等于true的话,代表有商品
    private boolean flag = false;

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    //消费者获取商品
    public synchronized void get(){
        /*
        * 如果flag等于false的话,意味着生产者没有生产商品,此时消费者无法消费,需要让消费者线程进入到阻塞状态,等待生产者生产,当
        * 有商品之后,再开始消费
        * */
        if (!flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("消费者取走了"+this.getBrand()+"----"+this.getName());
        flag = false;
        //唤醒生产者去进行生产
        notify();
    }
    //生产者生产商品
    public synchronized void set(String brand,String name){
        //当生产者抢占到cpu资源之后会判断当前对象是否有值,如果有的话,以为着消费者还没有消费,需要提醒消费者消费,同时
        //当前线程进入阻塞状态,等待消费者取走商品之后,再次生产,如果没有的话,不需要等待,不需要进入阻塞状态,直接生产即可
        if(flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
       this.setBrand(brand);
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        this.setName(name);
        System.out.println("生产者生产了" + this.getBrand() + "--" + this.getName());
        //如果代码执行到此处,意味着已经生产完成,需要将flag设置为true
        flag = true;
        //唤醒消费者去进行消费
        notify();
    }
}

然后到生产者Producer:
我们测试10个数据。

public class Producer implements Runnable {

    private Goods goods;

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

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i % 2 == 0) {
                goods.set("娃哈哈","矿泉水");
            } else {
                goods.set("旺仔","小馒头");
            }
        }
    }
}

接着就是Consumer:

public class Consumer implements Runnable {

    private Goods goods;

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

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            goods.get();
        }
    }
}

最后,我们写个Test类运行程序:

public class Test {
    public static void main(String[] args) {
        Goods goods = new Goods();

        Producer producer = new Producer(goods);
        Consumer consumer = new Consumer(goods);

        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
    }
}

输出结果:
在这里插入图片描述
以上是比较简单的做法,下面来说一种复杂点的做法。

使用阻塞队列来解决问题

先写Goods类:

这个比较容易,就写个构造函数和两个属性的get和set方法就好。

Consumer和Producer的方法就不在这里写了。

public class Goods {
    private String brand;
    private String name;

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

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

再来看看ConsumerQueue类,需要用到BlockingQueue,先介绍一下BlockingQueue:

在Java中,BlockingQueue是一个接口,它的实现类有ArrayBlockingQueue、DelayQueue、 LinkedBlockingDeque、LinkedBlockingQueue等,它们的区别主要体现在存储结构上或对元素操作上的不同,但是对于take与put操作的原理,却是类似的。

引用出处:不怕难之BlockingQueue及其实现—天外流星for

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

接下来,看看ConsumerQueue类的代码:

----先使用泛型来定义阻塞队列,再在run中用take()从队列中取出商品。

import java.util.concurrent.BlockingQueue;


public class ConsumerQueue implements Runnable {
	//此处使用泛型来定义阻塞队列
    private BlockingQueue<Goods> blockingQueue;
	//构造方法
    public ConsumerQueue(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            try {
            	//直接从队列中取出商品
                Goods goods = blockingQueue.take();
                System.out.println("消费者消费的商品是:"+goods.getBrand()+"--"+goods.getName());
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

再看看ProducerQueue类:


import java.util.concurrent.BlockingQueue;

public class ProducerQueue implements Runnable {
	//这里和ConsumerQueue一样,定义阻塞对列和构造函数
    private BlockingQueue<Goods> blockingQueue;
    
    public ProducerQueue(BlockingQueue blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    @Override
    public void run() {
        for(int i = 0;i<10;i++){
            Goods goods = null;
            //先定义好商品Goods类
            if(i%2==0){
                goods = new Goods("娃哈哈","矿泉水");
            }else{
                goods = new Goods("旺仔","小馒头");
            }
            System.out.println("生产者开始生产商品:"+goods.getBrand()+"--"+goods.getName());
            try {
            //这里用put()方法将商品放入阻塞队列中,等待消费者取出
                blockingQueue.put(goods);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

最后看Test类。

先介绍一下ArrayBlockingQueue:

ArrayBlockingQueue底层是使用一个数组实现队列的,并且在构造ArrayBlockingQueue时需要指定容量,也就意味着底层数组一旦创建了,容量就不能改变了,因此ArrayBlockingQueue是一个容量限制的阻塞队列。因此,在队列全满时执行入队将会阻塞,在队列为空时出队同样将会阻塞。

put(E e)方法在队列不满的情况下,将会将元素添加到队列尾部,如果队列已满,将会阻塞,直到队列中有剩余空间可以插入。

在这里插入图片描述
在这里插入图片描述

引用出处:不怕难之BlockingQueue及其实现—天外流星for

在这里插入图片描述


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;


public class Test {
    public static void main(String[] args) {
        BlockingQueue<Goods> queue = new ArrayBlockingQueue<Goods>(5);
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();


    }
}

输出结果和上一种做法一样的:

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值