源码解读之(八)ConcurrentLinkedQueue(1)

本文详细解释了JavaConcurrentLinkedQueue中的poll、offer、peek、size和remove操作原理,以及它们在并发环境中的工作机制,特别关注了CAS(CompareandSwap)在队列管理中的应用。此外,还提到了ConcurrentLinkedQueue在实际开发中的问题和调试技巧。
摘要由CSDN通过智能技术生成

四、poll操作


这个方法是获取头部的这个节点,如果队列为空则返回null

public E poll() {

// 这里其实就是一个goto的标记,用于跳出for循环

restartFromHead:

// [1]

for (;😉 {

for (Node h = head, p = h, q;😉 {

E item = p.item;

// [2] 如果当前节点中存的值不为空,则CAS设置为null

if (item != null && p.casItem(item, null)) {

// [3] CAS成功就更新头节点的位置

if (p != h)

updateHead(h, ((q = p.next) != null) ? q : p);

return item;

}

// [4] 当前队列为空,就返回null

else if ((q = p.next) == null) {

updateHead(h, p);

return null;

}

// [5] 当前节点和下一个节点一样,说明节点自引用,则重新找头节点

else if (p == q)

continue restartFromHead;

// [6]

else

p = q;

}

}

}

final void updateHead(Node h, Node p) {

if (h != p && casHead(h, p))

h.lazySetNext(h);

}

分为几种情况:

第一种情况是线程A调用poll方法的时候,发现队列是空的,即头节点和尾节点都指向哨兵节点,就会直接到[4],返回null

第二种情况,线程A执行到了[4],此时有一个线程却调用offer方法添加了一个节点,下图所示,那么此时线程A就不会走[4]了,[5]也不满足,于是会到[6]这里来,然后线程A又会跳到[1]处进行循环,此时p指向的节点中item不为null,所以会到[2]中;

在这里插入图片描述

到了这里还没完,还记不记得offer方法中有一个地方的代码没有执行的啊!就是这种情况,尾节点自己引用自己,我们再调用offer会怎么样呢?

回到offer方法,先会到[1],然后q指向自己这个哨兵节点(注意,此时虽然p指向的节点中存的是null,但是p!=null},于是再到[5],此时的图如下左图所示;此时由于t==tail,所以p=head

在这里插入图片描述

再在offer方法循环一次,此时q指向null,下面左图所示,然后就可以进入[2]中进行CASCAS成功,因为此时p!=t,所以还要进行CAStail指向新节点,下面右图所示,可以让GC回收那个垃圾!

在这里插入图片描述

五、peek操作


这个方法的作用就是获取队列头部的元素,只获取不移除,注意这个方法和上面的poll方法的区别啊!

public E peek() {

// [1] goto标志

restartFromHead:

for (;😉 {

for (Node h = head, p = h, q;😉 {

// [2]

E item = p.item;

// [3]

if (item != null || (q = p.next) == null) {

updateHead(h, p);

return item;

}

// [4]

else if (p == q)

continue restartFromHead;

// [5]

else

p = q;

}

}

}

final void updateHead(Node h, Node p) {

if (h != p && casHead(h, p))

h.lazySetNext(h);

}

1、如果队列中为空的时候,走到[3]的时候,就会如下图所示,由于h==p,所以updateHead方法啥也不做,然后返回就返回itemnull

在这里插入图片描述

2、如果队列不为空,那么如下左图所示,此时进入循环内不满足条件,会到[5]这里,将p指向q,然后再进行一次循环到[3],将q指向p的后一个节点,下面右图所示;

在这里插入图片描述

3、然后调用updateHead方法,用CAS将头节点指向p这里,然后将h自己指向自己,下图所示,最后返回item

在这里插入图片描述

六、size操作


获取当前队列元素个数,在并发环境下不是很有用,因为使用CAS没有加锁,所以从调用size函数到返回结果期间有可能增删元素,导致统计的元素个数不精确。

public int size() {

int count = 0;

for (Node p = first(); p != null; p = succ§)

if (p.item != null)

// 最大返回Integer.MAX_VALUE

if (++count == Integer.MAX_VALUE)

break;

return count;

}

// 获取第一个队列元素(哨兵元素不算),没有则为null

Node first() {

restartFromHead:

for (;😉 {

for (Node h = head, p = h, q;😉 {

boolean hasItem = (p.item != null);

if (hasItem || (q = p.next) == null) {

updateHead(h, p);

return hasItem ? p : null;

}

else if (p == q)

continue restartFromHead;

else

p = q;

}

}

}

// 获取当前节点的next元素,如果是自引入节点则返回真正头节点

final Node succ(Node p) {

Node next = p.next;

return (p == next) ? head : next;

}

七、remove操作


如果队列里面存在该元素则删除给元素,如果存在多个则删除第一个,并返回true,否者返回false

public boolean remove(Object o) {

// 查找元素为空,直接返回false

if (o != null) {

Node next, pred = null;

for (Node p = first(); p != null; pred = p, p = next) {

boolean removed = false;

E item = p.item;

// 相等则使用cas值null,同时一个线程成功,失败的线程循环查找队列中其他元素是否有匹配的。

if (item != null) {

if (!o.equals(item)) {

// 获取next元素

next = succ§;

continue;

}

removed = p.casItem(item, null);

}

next = succ§;

// 如果有前驱节点,并且next不为空则链接前驱节点到next,

if (pred != null && next != null)

pred.casNext(p, next);

if (removed)

return true;

}

}

return false;

}

八、ConcurrentLinkedQueue遇到的问题


这边debugger调试ConcurrentLinkedQueue遇到的一个坑!!!

测试主类:

public class ConcurrentLinkedQueueLocalTest {

public static void main(String[] args) {

ConcurrentLinkedQueueLocal queue = new ConcurrentLinkedQueueLocal();

queue.offer(1);

queue.offer(2);

}

}

复制源码到本地并重命名为ConcurrentLinkedQueueLocal

其中第二个分支和第三个分支我加了日志打印输出

@Override

public boolean offer(E e) {

checkNotNull(e);

final Node newNode = new Node(e);

for (Node t = tail, p = t;😉 {

Node q = p.next;

if (q == null) {

// p is last node

if (p.casNext(null, newNode)) {

// Successful CAS is the linearization point

// for e to become an element of this queue,

// and for newNode to become “live”.

if (p != t) // hop two nodes at a time

casTail(t, newNode); // Failure is OK.

return true;

}

// Lost CAS race to another thread; re-read next

}

else if (p == q) {

System.out.println(“第二次走了p==q分支”);

// We have fallen off list. If tail is unchanged, it

// will also be off-list, in which case we need to

// jump to head, from which all live nodes are always

// reachable. Else the new tail is a better bet.

p = (t != (t = tail)) ? t : head;

} else {

System.out.println(“第二次走了最后的else分支”);

// Check for tail updates after two hops.

p = (p != t && t != (t = tail)) ? t : q;

}

}

}

最后

经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。







由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

l)) ? t : q;

}

}

}

最后

经过日积月累, 以下是小编归纳整理的深入了解Java虚拟机文档,希望可以帮助大家过关斩将顺利通过面试。
由于整个文档比较全面,内容比较多,篇幅不允许,下面以截图方式展示 。

[外链图片转存中…(img-K9q7YziU-1714753437646)]
[外链图片转存中…(img-4GB9Wwcy-1714753437647)]
[外链图片转存中…(img-FlcQd4UF-1714753437647)]
[外链图片转存中…(img-rJbn7KAh-1714753437648)]
[外链图片转存中…(img-7W0pLgfx-1714753437649)]
[外链图片转存中…(img-8x14oXNM-1714753437649)]
[外链图片转存中…(img-zgdtv4YR-1714753437649)]

由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值