我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题
Posted bangiao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题相关的知识,希望对你有一定的参考价值。
前言: 本渣渣想分析分析
Doug Lea
大佬对高并发代码编写思路, 于是找到了我们今天的小主角ConcurrentLinkedQueue
进行鞭打, 说实话草稿我都打好了, 就差临门一脚, 给踢折了
直接看问题, idea
在Debug
和非Debug
模式下运行结果不同, vscode复现, eclipse毫无鸭梨
怎么发现的问题?
从这段代码开始
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.add("zhazha");
// 在下面这行下断点
Field headField = queue.getClass().getDeclaredField("head");
headField.setAccessible(true);
Object head = headField.get(queue);
Field itemField = queue.getClass().getDeclaredField("ITEM");
itemField.setAccessible(true);
VarHandle ITEM = (VarHandle) itemField.get(head);
Object o = ITEM.get(head);
System.out.println(o);
}
你会发现一个神奇的现象, 如果我们下断点在Field headField = queue.getClass().getDeclaredField("head");
这一行代码, 单步执行下来会发现System.out.println(o);
打印出了zhazha
, 但是如果不下断点, 直接运行打印null
为了防止是
WARNING: An illegal reflective access operation has occurred
警告的影响, 我改了改源码, 用unsafe获取试试
private static Unsafe unsafe;
static {
Class<Unsafe> unsafeClass = Unsafe.class;
Unsafe unsafe = null;
try {
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
ConcurrentLinkedQueueDemo.unsafe = (Unsafe) unsafeField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.add("zhazha");
// 在下面这行下断点
long headOffset = unsafe.objectFieldOffset(queue.getClass().getDeclaredField("head"));
Object head = unsafe.getObject(queue, headOffset);
long itemOffset = unsafe.staticFieldOffset(ConcurrentLinkedQueue.class.getDeclaredField("ITEM"));
Object base = unsafe.staticFieldBase(ConcurrentLinkedQueue.class.getDeclaredField("ITEM"));
VarHandle ITEM = (VarHandle) unsafe.getObject(base, itemOffset);
Object o = ITEM.get(head);
System.out.println(o);
}
完美复现
第一反应我的问题
去源码里看看怎么回事. 但.......这...........
仔细看红箭头的地址, t
、p
、head
和tail
都是同一个地址, 看上面的代码发现全是tail
赋值给这三个变量的
而NEXT
源码
他的接收类是Node
, 接收字段是next
, 接收字段类型Node
看这源码的势头, NEXT
修改的是p
对象, 如果该对象的next
节点为null
, 则把newNode
设置到节点上, 此时p
对象指向的是tail
, 同时head
也是指向的tail
节点, 所以这句话执行完毕, head.next
和tail.next
同样都是newNode
节点
但.....................这.....................
head
节点被直接替换掉, tail
保持不变
此时我的表情应该是这样
怀疑猫生
private static Unsafe unsafe;
static {
Class<Unsafe> unsafeClass = Unsafe.class;
Unsafe unsafe = null;
try {
Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
ConcurrentLinkedQueueDemo.unsafe = (Unsafe) unsafeField.get(null);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.add("zhazha");
// 在这里下断点
Class<? extends ConcurrentLinkedQueue> queueClass = queue.getClass();
Object head = unsafe.getObject(queue, unsafe.objectFieldOffset(queueClass.getDeclaredField("head")));
Field itemField = queueClass.getDeclaredField("ITEM");
itemField.setAccessible(true);
VarHandle ITEM = (VarHandle) itemField.get(queue);
Object item = ITEM.get(head);
System.out.println(item); // zhazha
long itemOffset = unsafe.staticFieldOffset(queueClass.getDeclaredField("ITEM"));
Object base = unsafe.staticFieldBase(queueClass.getDeclaredField("ITEM"));
VarHandle ITEM2 = (VarHandle) unsafe.getObject(base, itemOffset);
Object item2 = ITEM2.get(head);
System.out.println(item2); // zhazha
}
单步调试出来还是zhazha
, 而且为了防止反射出了问题, 我同时用了Unsafe
和反射两种方法
copy 源码添加自己的调试函数再次测试
得了得了, 放终极大招试试, copy ConcurrentLinkedQueue
源码出来改成MyConcurrentLinkedQueue
在offer
方法添加几个输出
public boolean offer(E e) {
final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
for (Node<E> t = tail, p = t; ; ) {
Node<E> q = p.next;
if (q == null) {
if (NEXT.compareAndSet(p, null, newNode)) {
System.out.println("this.head.item = " + this.head.item);
System.out.println("this.tail.item = " + this.tail.item);
System.out.println("this.head.next.item = " + this.head.next.item);
System.out.println("this.tail.next.item = " + this.tail.next.item);
if (p != t) {
TAIL.weakCompareAndSet(this, t, newNode);
}
return true;
}
}
else if (p == q) {
p = (t != (t = tail)) ? t : head;
}
else {
p = (p != t && t != (t = tail)) ? t : q;
}
}
}
主函数就比较简单了直接
public static void main(String[] args) {
MyConcurrentLinkedQueue<String> queue = new MyConcurrentLinkedQueue<String>();
queue.add("zhazha");
}
直接在非Debug
模式下运行, 发现打印出来的是
this.head.item = null
this.tail.item = null
this.head.next.item = zhazha
this.tail.next.item = zhazha
Process finished with exit code 0
在Debug
模式下单步运行发现
this.head.item = zhazha
this.tail.item = null
Exception in thread "main" java.lang.NullPointerException
at com.zhazha.juc.MyConcurrentLinkedQueue.offer(MyConcurrentLinkedQueue.java:117)
at com.zhazha.juc.MyConcurrentLinkedQueue.add(MyConcurrentLinkedQueue.java:67)
at com.zhazha.juc.MyConcurrentLinkedQueueDemo.main(MyConcurrentLinkedQueueDemo.java:13)
Process finished with exit code 1
纳尼?
不信邪的我在NEXT cas操作的前后增加了sleep
方法, 以非Debug
模式下运行
this.head.item = null
this.tail.item = null
this.head.next.item = zhazha
this.tail.next.item = zhazha
还是不一样
多环境IDE测试
放终极终极终极SVIP大招 ===> 放在eclipse上试试??? 或者vscode上???
在vscode上以Debug
模式单步运行输出
this.head.item = zhazha
this.tail.item = null
Exception in thread "main" java.lang.NullPointerException
at MyConcurrentLinkedQueue.offer(MyConcurrentLinkedQueue.java:116)
at MyConcurrentLinkedQueue.add(MyConcurrentLinkedQueue.java:66)
at MyConcurrentLinkedQueueDemo.main(MyConcurrentLinkedQueueDemo.java:11)
非Debug
模式直接输出
this.head.item = null
this.tail.item = null
this.head.next.item = zhazha
this.tail.next.item = zhazha
在eclipse上以Debug
模式单步运行输出
this.head.item = null
this.tail.item = null
this.head.next.item = zhazha
this.tail.next.item = zhazha
非Debug
运行输出
this.head.item = null
this.tail.item = null
this.head.next.item = zhazha
this.tail.next.item = zhazha
发现了没有? 还是我大eclipse
坚挺住了
以上是关于我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题的主要内容,如果未能解决你的问题,请参考以下文章
(Android Studio)如何仅通过我的代码进行调试?
是否可以在调试版本中编译 VS 项目,包括要通过不同项目调试的断点?
VSCode 新手:通过 Docker 进行远程 Jest/Node 调试