Loading

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

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

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

完美复现

第一反应我的问题

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

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

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

看这源码的势头, NEXT修改的是p对象, 如果该对象的next节点为null, 则把newNode设置到节点上, 此时p对象指向的是tail, 同时head也是指向的tail节点, 所以这句话执行完毕, head.nexttail.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坚挺住了

posted @ 2020-06-29 12:13  bangiao  阅读(221)  评论(0编辑  收藏  举报