您当前所在位置:首页攻略生产者消费者模式,以及基于BlockingQueue的快速实现

生产者消费者模式,以及基于BlockingQueue的快速实现

更新:2024-08-28 10:04:19编辑:游戏资讯归类:攻略

什么是生产者消费者模式?
简单来说就是有两个角色,一个角色主要负责生产数据,一个角色主要负责消费(使用)数据。
那么生产者直接依赖消费者,然后直接调用是否可以?
答案是可以的,但是有些场景无法及时解决,典型的就是生产者消费者的速度无法同步,导致整体的速度上不去的情况。执行速度永远取决于二者的最小速度(假设生产者和消费者的速度时快时慢)。
一般的解决方案是什么呢?
我们通常会加一个中间件,作为缓冲队列。

生产者生产好的数据丢到缓冲队列中,消费者根据自身情况及时获取数据,处理数据,如下图:

典型的实现就是Mq,比如Rocket mq、rabbit mq等,这是微服务、分布式场景下一个常见的中间件。

在服务内部,我们通常也会出现生产者消费者的模式,这时候引入mq显然有一些过重,我们需要更高效,更简洁的办法。
最简单的办法就是用一个queue,或者用一个List,生产者向队尾增加数据,消费者从队头取数据。
但是这样做首先会存在多线程共享数据的场景,需要做一些锁来解决线程同步的问题。
其次消费者在抢占数据时,我们又希望可以区分公平抢占和非公平抢占锁,
同时我们还要考虑缓冲队列是否要有界,如果无界的话,资源是否会存在耗尽问题,
而且我们还要考虑是否可以更优雅的平衡生产者和消费者的处理速度,比如这样:
如果缓冲队列满了,那么生产者线程自己会挂起停止生产,当队列有空余空间时(注意不是全部清空数据),生产者会被唤醒继续生产;
如果缓冲队列空了,那么消费者线程自己会挂起停止消费,当队列有数据时(注意不是全部堆满数据),消费者会被唤醒继续消费。
诸如此类等等问题,如果我们想开发这样一个缓冲队列,要考虑和解决还是比较有难度的。
而java8 提供的工具包早已看穿了这一切,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )并提供了一套优雅的解决方案: BlockingQueue
BlockingQueue 是一个接口定义,我们实际使用的是下边的各个实现类:

BlockingQueue 中常用的方法如下,记住这些代码不要死记,只要记得有这回事,需要的时候查看一下源码即可:

除此之外还会有获取指定元素(不移除)E element()/E peek(),删除指定元素boolean remove(Object o)等方法这里就先不赘述了。

下面我们来看一组简单的示例
生产者生产三种礼物gift: 手机/电脑/LV包
消费者通通消费
生产者/消费者都使用线程池来维护,具体情况如下代码:

主类

产品类

生产者:

消费者:

执行后效果如下:

注意由于是多线程在操作,所以某一秒处的日志可能是乱序的,

感兴趣的同学可以自行分析下

接下来简单说下BlockingQueue的几种实现方式的内部逻辑和使用场景
1、ArrayBlockingQueue
内部使用数组实现缓冲区,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )有界队列;
使用ReentrantLock锁来保证线程安全
可以满足大部分的业务场景。

2、LinkedBlockingQueue
内部使用了单链表实现了缓冲区,可以是无界队列,也可以是有界队列;
使用读写锁来保证线程安全。
适用于队列弹性比较大,并发性要求高的场景。

3、SynchronousQueue
同步队列
很多人称如果想要任务快速执行,可以使用该队列。容易让别人有误解,不如我们来举个实际的例子
将主类中的blockingqueue 切换为SynchronousQueue,

输入为下,下面是前13秒是的输出,红色字体为我的解释:

注意最后的时间点 15:35:33.916

消费者第二次消费完,iPhone的厂商立刻打印了日志,所以之前iPhone的厂商在放置数据的过程中,一直处于一个阻塞的状态。
也就是如果消费者不取,那么生产者就一直处于呈现状态。这恰恰是如果是快速消费的场景,可以使用此方案,而不是此方案可以让你的任务快速执行。当然我们通过名字其实也可以判断。

这种场景现实中有么?
也是有的,比如服务员找零,一般会将钱放在手里,等你拿了钱才会做接下来的动作。像这种需要同步交接,快速响应的场景,那么就可以考虑SynchronousQueue。
当然实际场景中,服务员也不可能一直等着,消费者可能一直不拿钱(超时设定),消费者不收钱(通过InterruptedException 打断阻塞)。

4、PriorityBlockingQueue
优先级的阻塞队列,想法其实很简单,就是消费者不再是先进先出的获取数据,而是允许根据优先级排序后,优先拿优先级高的产品。队列内部是通过最小堆来维护队列的,我们在创建队列时,
要指定比较算法。如下,我们使用的是按照价格由高到低的方式获取产品,同时调整生产者速度>消费者速度(调整生产者sleep 为1000ms即可):

输出结果如下,我们可以看到消费者只获取当前队列中价值最高的产品:

5、DelayQueue

这个队列也很有意思,它需要有产品实现一个delay接口,接口实时的返回还要多久这个产品可用。
如下我们切换为 DelayQueue。
效果如下:
消费者即使已经触发获取,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )但是还是无法立刻拿到,需要在delay()返回非正数以后才能获取。这样生产者可以生产一些内部有时间概念的产品,起到一个定时器的作用。
举个例子:
我们装修了一间房子,由于装修有害物质,我们需要6个月后才能入住,

常用的办法如下:
1、添加定时器,由定时器来触发,但是显然要有外部依赖其他对象。
2、反复获取,拿到之后check时间,如果未到时间,重新丢回队列,但是这样显然过于复杂,系统的调用频次也会上去。
3、消费者来获取产品时,判断时间是否可取,如果不可取就一直等待到可取到产品为止。注意此时消费线程会阻塞到该队列头部的产品,并不会越过它尝试拿后续的产品。(DelayQueue选用此种策略)

我们先修改缓冲队列为DelayQueue

生产者的时间周期改为2s,消费者的时间周期改为4s , 假设产品的过期时间是5s 注意这几个时间节点,

Gift 类的代码我们需要改造一下,方便定位分析:

效果大概是这样的:

是不是和我们设想的不太一样,直接说发生了什么,注意看不同颜色的分析对应的不同的颜色的日志:

首先是线程5 消费者抢到了锁,则直接等待到产品到期,注意线程5不是按照我们约定的周期检查的,然后二次确认时间,接着就消费了(24秒--->29秒)

此时线程4抢到了下一把锁 注意下一个礼物是最新生产的产品,而不是最早生产的产品 然后等待产品到期时间,二次确认并消费(28秒--->33秒)注意在二次确认的时间的时候,两个线程都有确认,但是只有一个线程真正会消费成功,这不影响使用效果(猜测这里是为了提高性能,并没有完全,没有采用很重的锁)。

接下来的逻辑一样

以上我们可以得到两个重要结论:

DelayQueue并不是按照顺序进行消费的,而是获取最新的的数据进行尝试获取,(尽管此时队列中已经存在可用的产品)

DelayQueue唤醒消费者的时间,是按照产品的到达可用状态的时间后,才会唤醒

因此使用DelayQueue时,我们要注意,生产商品不能太激烈,生产速度不要大于消费速度,否则产品很可能消费不到了(当然也可以利用这个规则,将旧的产品进行淘汰处理)

产品的delay时间不能太长,否则可能会导致消费者一直处于挂起状态。

以上就是电脑114游戏给大家带来的关于生产者消费者模式,以及基于BlockingQueue的快速实现全部内容,更多攻略请关注电脑114游戏。

电脑114游戏-好玩游戏攻略集合版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

以闪亮之名汪汪奇遇记活动详情 谁是你爸爸 中文版手机版