这个方法是获取头部的这个节点,如果队列为空则返回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]
中进行CAS
,CAS
成功,因为此时p!=t
,所以还要进行CAS
将tail
指向新节点,下面右图所示,可以让GC
回收那个垃圾!
这个方法的作用就是获取队列头部的元素,只获取不移除,注意这个方法和上面的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
方法啥也不做,然后返回就返回item
为null
。
2、如果队列不为空,那么如下左图所示,此时进入循环内不满足条件,会到[5]
这里,将p
指向q
,然后再进行一次循环到[3]
,将q
指向p
的后一个节点,下面右图所示;
3、然后调用updateHead
方法,用CAS
将头节点指向p
这里,然后将h
自己指向自己,下图所示,最后返回item
获取当前队列元素个数,在并发环境下不是很有用,因为使用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;
}
如果队列里面存在该元素则删除给元素,如果存在多个则删除第一个,并返回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;
}
这边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)]
由于篇幅限制,文档的详解资料太全面,细节内容太多,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!