--设计模式--生产者/消费模式三种实现(Java实现)

15 篇文章 0 订阅
2 篇文章 0 订阅

■ 什么是生产者/消费模式

在工作中,大家可能会碰到这样一种情况:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。在生产者与消费者之间在加个缓冲区,我们形象的称之为仓库,生产者负责往仓库了进商品,而消费者负责从仓库里拿商品,这就构成了生产者消费者模式。

打个通俗的例子,就好比你写信,放入邮筒,取件员取走信这一些过程。你写信,就是生产的过程,而你将信放入邮筒则是将数据存入缓冲区,而快递员从邮筒取走你的信,相当于消费者从缓冲区将你的数据消费了。

■ 使用生产者/消费模式的好处

借用这篇文章所讲的,说的挺好的,感兴趣的同学可以看看:网页链接

1.解耦

假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。

接着上述的例子,如果不使用邮筒(也就是缓冲区),你必须得把信直接交给邮递员。有同学会说,直接给邮递员不是挺简单的嘛?其实不简单,你必须得认识谁是邮递员,才能把信给他(光凭身上穿的制服,万一有人假冒,就惨了)。这就产生和你和邮递员之间的依赖(相当于生产者和消费者的强耦合)。万一哪天邮递员换人了,你还要重新认识一下(相当于消费者变化导致修改生产者代码)。而邮筒相对来说比较固定,你依赖它的成本就比较低(相当于和缓冲区之间的弱耦合)。

2.支持并发

生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。

使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种,后面的帖子会讲两种并发类型下的应用)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度

其实当初这个模式,主要就是用来处理并发问题的。

从寄信的例子来看。如果没有邮筒,你得拿着信傻站在路口等邮递员过来收(相当于生产者阻塞);又或者邮递员得挨家挨户问,谁要寄信相当于消费者轮询)。不管是哪种方法,都挺土的。

3.支持忙闲不均

缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。

为了充分复用,我们再拿寄信的例子来说事。假设邮递员一次只能带走1000封信。万一某次碰上情人节(也可能是圣诞节)送贺卡,需要寄出去的信超过1000封,这时候邮筒这个缓冲区就派上用场了。邮递员把来不及带走的信暂存在邮筒中,等下次过来时再拿走。

■ 三种代码实现

1.使用synchronized和wait()、notifyAll()

使用wait()让当前线程等待并释放锁,让notifyAll()唤醒沉睡的线程让它继续执行

public static void main(String[] args) {
        Resource resource = new Resource();
        //生产者线程
        Producer p1 = new Producer(resource);
        Producer p2 = new Producer(resource);
        Producer p3 = new Producer(resource);

        //消费者线程
        Consumer c1 = new Consumer(resource);
        Consumer c2 = new Consumer(resource);
        Consumer c3 = new Consumer(resource);

        //启动生产
        p1.start();
        p2.start();
        p3.start();

        //启动消费
        c1.start();
        //c2.start();
        //c3.start();
    }

    static class Resource {
        //库存拥有数量  volatile关键字修饰
        private volatile int num = 0;

        //仓库最大容量
        private int size = 10;

        public synchronized void remove() {
            if (num > 0 && num <= size) {
                num--;
                notifyAll();  //通知生产者制造资源
                System.out.println("消费者:" + Thread.currentThread().getName() + "消耗了一件资源," + "当前库存还有:" + num + "个");
            } else {
                try {
                    System.out.println("消费者:" + Thread.currentThread().getName() + "线程即将进入等待状态...");
                    //如果没有资源,则使消费者进入等待状态
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public synchronized void add() {
            //仓库未满
            if (num < size && num >= 0) {
                num++;
                notifyAll();  // 通知等待的消费者
                System.out.println("生产者:"+Thread.currentThread().getName() + "生产了一件资源,当前库存有:" + num + "个");
            } else {
                try {
                    System.out.println("生产者:"+Thread.currentThread().getName() + "线程即将进入等待状态...");
                    //仓库满了,等待消费者拿出库存
                    wait();
                    //创建 就绪 阻塞 运行 死亡
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 消费者线程
    static class Consumer extends Thread {
        private Resource resource1;

        public Consumer(Resource resource) {
            resource1 = resource;
        }

        @Override
        public void run() {
            //消费者不断消费
            while (true) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                resource1.remove();  //消费者取出库存
            }
        }
    }

    //生产者线程
    static class Producer extends Thread {
        private Resource resource1;

        public Producer(Resource resource) {
            resource1 = resource;
        }

        @Override
        public void run() {
            //生产者不断生产
            while (true) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                resource1.add();  //生产者添加库存
            }
        }
    }

运行结果,当然你们的可能和我不一样👇:

生产者:Thread-1生产了一件资源,当前库存有:1个
生产者:Thread-0生产了一件资源,当前库存有:2个
消费者:Thread-3消耗了一件资源,当前库存还有:1个
生产者:Thread-2生产了一件资源,当前库存有:2个
生产者:Thread-0生产了一件资源,当前库存有:3个
生产者:Thread-2生产了一件资源,当前库存有:4个
生产者:Thread-1生产了一件资源,当前库存有:5个
消费者:Thread-3消耗了一件资源,当前库存还有:4个
生产者:Thread-2生产了一件资源,当前库存有:5个
生产者:Thread-0生产了一件资源,当前库存有:6个
生产者:Thread-1生产了一件资源,当前库存有:7个
消费者:Thread-3消耗了一件资源,当前库存还有:6个
消费者:Thread-3消耗了一件资源,当前库存还有:5个
生产者:Thread-1生产了一件资源,当前库存有:6个
生产者:Thread-0生产了一件资源,当前库存有:7个
生产者:Thread-2生产了一件资源,当前库存有:8个
生产者:Thread-1生产了一件资源,当前库存有:9个
消费者:Thread-3消耗了一件资源,当前库存还有:8个
生产者:Thread-2生产了一件资源,当前库存有:9个
生产者:Thread-0生产了一件资源,当前库存有:10个
消费者:Thread-3消耗了一件资源,当前库存还有:9个
生产者:Thread-0生产了一件资源,当前库存有:10个
生产者:Thread-2线程即将进入等待状态…
生产者:Thread-1线程即将进入等待状态…
消费者:Thread-3消耗了一件资源,当前库存还有:9个
生产者:Thread-0生产了一件资源,当前库存有:10个
生产者:Thread-2线程即将进入等待状态…
生产者:Thread-0线程即将进入等待状态…
生产者:Thread-1线程即将进入等待状态…
消费者:Thread-3消耗了一件资源,当前库存还有:9个

2.使用Lock和Condiction的await()、signalAll()

如果不太懂Lock和Condiction得使用方法,可以看一下这篇文章:网页链接
注意要在finally后面使用lock.unlock()释放锁,需手动释放。

//    使用Lock 和 Condition解决生产者消费者问题
    public static void main(String[] args) {
    	//创建锁和条件
        Lock lock = new ReentrantLock();
        Condition producerCondition = lock.newCondition();
        Condition consumerCondition = lock.newCondition();
        Resource resource = new Resource(lock, producerCondition, consumerCondition);

		//生产者线程
        Producer producer1 = new Producer(resource);
        Producer producer2 = new Producer(resource);
        Producer producer3 = new Producer(resource);
		//消费者线程
        Consumer consumer1 = new Consumer(resource);
        Consumer consumer2 = new Consumer(resource);

        producer1.start();
        producer2.start();
        producer3.start();

        consumer1.start();
//        consumer2.start();
    }

    static class Producer extends Thread {

        private Resource resource;

        public Producer(Resource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    sleep((long) (Math.random() * 1000 + 300));  //生产者休息0.5-1.3s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                resource.add();
            }
        }
    }

    static class Consumer extends Thread {

        private Resource resource;

        public Consumer(Resource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    sleep((long) (Math.random() * 1000)); //消费者休息0-1s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                resource.remove();
            }
        }
    }


    static class Resource {
        private Lock lock;
        private Condition producer;
        private Condition customer;
        private volatile int num = 0;  //库存
        private int size = 10;  //仓库大小

        public Resource(Lock lock, Condition producer, Condition customer) {
            this.lock = lock;
            this.producer = producer;
            this.customer = customer;
        }

        public void add() {
            lock.lock();
            try {
                if (num < size && num >= 0) {
                    num++;
                    customer.signalAll();  //唤醒消费者
                    System.out.println("生产者"+Thread.currentThread().getName() + "正在生产一件库存,当前库存:" + num);
                } else {
                    System.out.println(Thread.currentThread().getName() + "即将进入等待状态...");
                    try {
                        producer.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();  //解锁
            }

        }

        public void remove() {
            lock.lock();
            try {
                if (num <= size && num > 0) {
                    num--;
                    producer.signalAll();  //唤醒生产者
                    System.out.println("消费者"+Thread.currentThread().getName() + "正在取出一件库存,当前库存:" + num);
                } else {
                    System.out.println(Thread.currentThread().getName() + "即将进入等待状态...");
                    try {
                        customer.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            } finally {
                lock.unlock();   //解锁
            }
        }

    }

执行结果如下👇:

生产者Thread-2正在生产一件库存,当前库存:1
生产者Thread-0正在生产一件库存,当前库存:2
消费者Thread-3正在取出一件库存,当前库存:1
消费者Thread-3正在取出一件库存,当前库存:0
生产者Thread-0正在生产一件库存,当前库存:1
生产者Thread-1正在生产一件库存,当前库存:2
消费者Thread-3正在取出一件库存,当前库存:1
生产者Thread-1正在生产一件库存,当前库存:2
消费者Thread-3正在取出一件库存,当前库存:1
生产者Thread-0正在生产一件库存,当前库存:2
生产者Thread-2正在生产一件库存,当前库存:3
生产者Thread-1正在生产一件库存,当前库存:4
生产者Thread-0正在生产一件库存,当前库存:5
生产者Thread-2正在生产一件库存,当前库存:6
消费者Thread-3正在取出一件库存,当前库存:5
生产者Thread-1正在生产一件库存,当前库存:6
生产者Thread-1正在生产一件库存,当前库存:7
生产者Thread-0正在生产一件库存,当前库存:8
消费者Thread-3正在取出一件库存,当前库存:7
生产者Thread-2正在生产一件库存,当前库存:8
生产者Thread-0正在生产一件库存,当前库存:9
生产者Thread-1正在生产一件库存,当前库存:10
消费者Thread-3正在取出一件库存,当前库存:9
生产者Thread-1正在生产一件库存,当前库存:10
Thread-2即将进入等待状态…
Thread-0即将进入等待状态…
消费者Thread-3正在取出一件库存,当前库存:9
消费者Thread-3正在取出一件库存,当前库存:8
消费者Thread-3正在取出一件库存,当前库存:7
消费者Thread-3正在取出一件库存,当前库存:6
生产者Thread-1正在生产一件库存,当前库存:7
生产者Thread-0正在生产一件库存,当前库存:8
生产者Thread-1正在生产一件库存,当前库存:9
消费者Thread-3正在取出一件库存,当前库存:8
生产者Thread-2正在生产一件库存,当前库存:9
消费者Thread-3正在取出一件库存,当前库存:8
生产者Thread-0正在生产一件库存,当前库存:9
消费者Thread-3正在取出一件库存,当前库存:8
消费者Thread-3正在取出一件库存,当前库存:7
生产者Thread-2正在生产一件库存,当前库存:8
消费者Thread-3正在取出一件库存,当前库存:7
生产者Thread-1正在生产一件库存,当前库存:8
生产者Thread-2正在生产一件库存,当前库存:9
生产者Thread-1正在生产一件库存,当前库存:10
消费者Thread-3正在取出一件库存,当前库存:9
生产者Thread-0正在生产一件库存,当前库存:10
消费者Thread-3正在取出一件库存,当前库存:9

3.使用阻塞队列BlockingQueue

声明方法:BlockingQueue resourceQueue = new LinkedBlockingQueue(10);
定义最大容量为10,无数据存入时大小为0。
常用方法:
put(e):用来将数据存入队列中,若队列满了则会阻塞
take():取走一个数据,若数据为空则会阻塞

public static void main(String[] args) {
        Resource resource = new Resource();

        Producer producer1 = new Producer(resource);
        Producer producer2 = new Producer(resource);
        Producer producer3 = new Producer(resource);

        Customer customer1 = new Customer(resource);
        Customer customer2 = new Customer(resource);
        Customer customer3 = new Customer(resource);

        producer1.start();
        producer2.start();

        customer1.start();
//        customer2.start();
//        customer3.start();
    }

    static class Resource {
        // 定义阻塞队列 最大容量为10 默认容量为0
        private BlockingQueue resourceQueue = new LinkedBlockingQueue(10);
//        private BlockingQueue resourceQueue = new ArrayBlockingQueue(10); 也是一种方式

        public void add() {
            try {
                //向队列中添加元素 内容可以随意取, 队列满时会阻塞
                resourceQueue.put(1);
                System.out.println("生产者" + Thread.currentThread().getName() + "正在生产一个库存,当前库存为:" + resourceQueue.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        public void remove() {
            try {
                resourceQueue.take();  //取出一个库存
                System.out.println("消费者" + Thread.currentThread().getName() + "正在取出库存,当前库存为:" + resourceQueue.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class Customer extends Thread {
        Resource resource;

        public Customer(Resource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                resource.remove();
            }
        }
    }

    static class Producer extends Thread {
        Resource resource;

        public Producer(Resource resource) {
            this.resource = resource;
        }

        @Override
        public void run() {
            while (true) {
                try {
                    sleep( 1000 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                resource.add();
            }
        }
    }

执行结果如下,可以观察一下库存的变化👇:

生产者Thread-1正在生产一个库存,当前库存为:2
消费者Thread-3正在取出库存,当前库存为:1
生产者Thread-0正在生产一个库存,当前库存为:2
消费者Thread-3正在取出库存,当前库存为:1
生产者Thread-0正在生产一个库存,当前库存为:1
生产者Thread-1正在生产一个库存,当前库存为:2
消费者Thread-3正在取出库存,当前库存为:1
生产者Thread-1正在生产一个库存,当前库存为:3
生产者Thread-0正在生产一个库存,当前库存为:2
生产者Thread-0正在生产一个库存,当前库存为:4
消费者Thread-3正在取出库存,当前库存为:4
生产者Thread-1正在生产一个库存,当前库存为:4
生产者Thread-1正在生产一个库存,当前库存为:5
消费者Thread-3正在取出库存,当前库存为:5
生产者Thread-0正在生产一个库存,当前库存为:5
生产者Thread-1正在生产一个库存,当前库存为:5
消费者Thread-3正在取出库存,当前库存为:6
生产者Thread-0正在生产一个库存,当前库存为:6
消费者Thread-3正在取出库存,当前库存为:5
生产者Thread-0正在生产一个库存,当前库存为:7
生产者Thread-1正在生产一个库存,当前库存为:7
生产者Thread-1正在生产一个库存,当前库存为:8
消费者Thread-3正在取出库存,当前库存为:8
生产者Thread-0正在生产一个库存,当前库存为:8
生产者Thread-0正在生产一个库存,当前库存为:9
消费者Thread-3正在取出库存,当前库存为:9
生产者Thread-1正在生产一个库存,当前库存为:9
生产者Thread-0正在生产一个库存,当前库存为:10
消费者Thread-3正在取出库存,当前库存为:10
生产者Thread-1正在生产一个库存,当前库存为:10
消费者Thread-3正在取出库存,当前库存为:9
生产者Thread-1正在生产一个库存,当前库存为:10
消费者Thread-3正在取出库存,当前库存为:9
生产者Thread-1正在生产一个库存,当前库存为:10

有人可能会问,为什么这库存数这么奇怪,生产之后还是生产,库存有些却没变,其实当另一个线程获取size时,另一个线程又制造了新的库存,之后又取就是会多了一件,而消费的一件已经被补回来了,所以size才会显示一样,可以这样理解:+1-1为获取size时的库存变化,不相信阻塞成功的同学可以再来看看这几张图:
在这里插入图片描述
运行结果:

生产者Thread-1正在生产一个库存,当前库存为:2
生产者Thread-0正在生产一个库存,当前库存为:2
生产者Thread-0正在生产一个库存,当前库存为:3
生产者Thread-1正在生产一个库存,当前库存为:4
生产者Thread-0正在生产一个库存,当前库存为:5
生产者Thread-1正在生产一个库存,当前库存为:6
生产者Thread-0正在生产一个库存,当前库存为:7
生产者Thread-1正在生产一个库存,当前库存为:8
生产者Thread-0正在生产一个库存,当前库存为:10
生产者Thread-1正在生产一个库存,当前库存为:10

然后程序便停一直停在了这,可以数一数共运行了多少次,发现刚好是10次,虽然它库存的显示数量有些奇怪,但并没影响到满队阻塞,只是有些线程在获取size时,另一个线程已经多制造了一个库存。

♦ 总结

生产者消费模式以我的思考来说就是很现实的问题,就是卖家没货了,买家想买,就必须等待卖家生产;而卖家想卖,库存满了没人买,则要等待买家买;而我们通过使用缓冲区将商品放入缓冲区,这样减少了买家和卖家的耦合关系,即双方都不用直接需要对方,而是通过缓冲区来获取商品,减少了双方等待的时长,使得双方能有序执行各自的任务,这是我的拙见,大家也可以想一想,加油!^^

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值