并发(9)——LinkedBlockingQueue 源码解析

本文详细解析了LinkedBlockingQueue的工作原理,包括offer、put、add的添加元素方法,以及remove、poll、take的移除元素方法。对比了与ArrayBlockingQueue在队列大小、数据存储、GC影响及锁机制上的差异。

LinkedBlockingQueue 需要掌握的知识点

  • 添加的三种方法的实现方式和区别:addofferput
  • 移除的三种方法的实现方式和区别:removepolltake

添加元素

1. offer() 方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

我们来梳理一下【图1】中的offer方法所做的事情。

  • 【步骤一】首先第一次判断队列是否已满,如果已经满了,则直接返回false
  • 【步骤二】如果第一次判断队列没有满,则对队列加锁putLock,加锁后第二次判断队列有没有满,因为这里存在高并发的情况,所以需要第二次判断队列是否满。
  • 【步骤三】如果第二次判断队列还是没有满,则向队列中加入结点。
  • 【步骤四】加入节点后,判断加入结点之后队列的长度是否超过了队列长度的限制(Integer.MAX_VALUE),如果没有超过则唤醒下一个添加元素的线程。
  • 【步骤五】这里在【第442行】判断了if(c == 0),如果 c 的值等于 0 说明队列中还有1条数据没有被消费,所以要唤醒消费线程,即调用signalNotEmpty方法。 (注意:这里为什么是 c=0 指还有一条数据没被消费,因为看【第420行】c 的初始值是 -1 )

这里需要注意的两点。

  • 唤醒添加线程的原因(【第435行】)。在添加元素完成后,会判断队列是否已经满了,如果队列不满就会继续唤醒在条件对象 notFull 上添加线程,注意这里与 ArrayBlockingQueue 的操作不一样。

    • 在ArrayBlockingQueue 内部完成添加元素的操作之后,会直接唤醒消费线程对元素进行获取,之所以这样操作,是因为 ArrayBlockingQueue 使用一个 ReentrantLock 对消费者和生产者进行控制,使用2个Condition进行消费和生成操作,在ArrayBlockingQueue中如果在添加完成后再次唤醒添加线程的话,消费线程可能永远都无法执行。
    • 在LinkedBlockingQueue中,生产者和消费者是不互斥的,即消费元素和生产元素可以同时进行。生产者线程直接唤醒生产者的其他线程,如果没有等待的生产者,则直接返回。如果有就等到所有生产者都执行完再结束。
  • 为什么判断if(c==0)时才去唤醒消费线程,因为消费线程一旦被唤醒就一直进行消费,所以c的值一直在不断变化,c值是添加完元素时队列的大小,这个时候c的值可能是 0 或者 大于0。如果 c=0,说明之前消费线程已经停止了,这多出的一个元素是刚刚生产出来的,所以需要调用消费者线程进行消费。如果c>0,那么消费线程不会被唤醒,只能等待下一个消费操作(take、poll、remove等),为什么 c>0 时,消费线程不会被唤醒呢? 因为 c>0 是上次调用的消费线程还没有消费完队列中的数据。

删除元素

1. remove方法

在这里插入图片描述

在这里插入图片描述

【注】trail 是 p 元素的前一个元素。

在这里插入图片描述

这里为什么要将 生产者 和 消费者 同时加锁呢?我的理解是,因为remove方法删除元素的位置是不确定的,如果恰好删除的元素在队头或者队尾,这样在队尾添加元素的时候会出现并发的问题。

2. poll 方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
这个操作与 add 操作相类似,这里不再赘述。

3. take方法

在这里插入图片描述

take 方法与poll的主要区别就是,take方法是可相应中断的方法,并且如果队列为空的话,就一直循环等待直到队列中有元素为止。

总结

  • LinkedBlockingQueue 是一个单向链表、范围任意的、FIFO阻塞队列;访问和移除元素在队头,添加元素在队尾。
LinkedBlockingQueueArrayBlockingQueue 的区别
  • 【区别一】队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当增加速度大于移动速度时,在无界的情况下,可能会造成内存溢出等问题。
  • 【区别二】数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
  • 【区别三】由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或者删除元素时不会产生或者销毁任何额外的独享实例,而LinkedBlockingQueue则会产生额外的Node对象。这可能在长时间内需要搞笑并发地处理大批量数据时,对GC可能存在较大的影响。
  • 【区别四】两者的实现队列添加或者移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加移动和移除操作采用的都是同一个ReentrantLock锁,而LinkedBlockingQueue实现的队列中的锁时分离的,其添加采用的是putLock,移除采用的是takeLock,这样可以提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。

参考并感谢

[1] https://blog.youkuaiyun.com/javazejian/article/details/77410889

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值