我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

前言: 本渣渣想分析分析Doug Lea大佬对高并发代码编写思路, 于是找到了我们今天的小主角ConcurrentLinkedQueue进行鞭打, 说实话草稿我都打好了, 就差临门一脚, 给踢折了

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

直接看问题, ideaDebug非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);
}

完美复现

第一反应我的问题

去源码里看看怎么回事. 但.......这...........

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

仔细看红箭头的地址, tpheadtail都是同一个地址, 看上面的代码发现全是tail赋值给这三个变量的
NEXT源码

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

他的接收类是Node, 接收字段是next, 接收字段类型Node

看这源码的势头, NEXT修改的是p对象, 如果该对象的next节点为null, 则把newNode设置到节点上, 此时p对象指向的是tail, 同时head也是指向的tail节点, 所以这句话执行完毕, head.nexttail.next同样都是newNode节点
但.....................这.....................

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

head节点被直接替换掉, tail保持不变

此时我的表情应该是这样

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

怀疑猫生

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和反射两种方法

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

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模式下运行

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

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)

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

非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

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

非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毫无问题

我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题

上一篇:洛谷 p5020 货币系统 题解


下一篇:apollo docker