并发包之队列
现在我们开始讨论一下并发容器中的队列的实现,队列在并发中经常会被使用,比如AQS中的同步队列、阻塞队列等。现在我们就好好研究一下队列是如何保证线程安全的,同时我们所知道的常用的消费者生产者模式也是通过队列来实现的。
同时我们知道Kafka就是一种消息队列。
常用的并发队列有阻塞队列和非阻塞队列,阻塞队列使用锁来实现,非阻塞队列使用CAS非阻塞算法实现。我们今天来分析一下ConcurrentLinkedQueue
是如何通过CAS
来实现的非阻塞队列。
ConcurrentLinkedQueue类的关注点
HOPS的设计
该类中的 tail 和 head 是延迟更新的,两者更新触发时机为:
tail 更新触发时机:当 tail
指向的节点的下一个节点不为 null
的时候,会执行定位队列真正的队尾结点的操作,找到队尾结点后完成插入之后才会通过 casTail
进行 tail
更新;当 tail
指向的节点的下一个结点为 null
的时候, 只插入结点不更新 tail
。
从上面的分析中可以看出,head 和 tail 的更新是“跳着的”,即中间总是间隔了一个。那么这样设计的意图是什么呢?
如果让 tail
永远作为队列的队尾结点,实现的代码量会更少,而且逻辑更易懂。但是,这样做有一个缺点,如果大量的入队操作,每次都要执行CAS
进行 tail 的更新,汇总起来对性能也会是大大的损耗。如果能减少 CAS
更新的操作,无疑可以大大提升入队的操作效率。
注意点
ConcurrentLinkedQueue
使用 CAS
非阻塞算法实现,使用CAS
解决了当前节点与next
节点之间的安全链接和对当前节点值的赋值。由于使用CAS
没有使用锁,所以获取size的时候有可能进行 【offer
】 、【pool
】或者【remove
】等才做,导致获取的元素个数不精确,所以在并发情况下size函数不是很有用。