zoukankan      html  css  js  c++  java
  • 我通过调试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坚挺住了

  • 相关阅读:
    程序员眼里IE浏览器是什么样的
    iOS UITableView 与 UITableViewController
    iOS 委托与文本输入(内容根据iOS编程编写)
    iOS 视图控制器 (内容根据iOS编程编写)
    iOS 视图:重绘与UIScrollView(内容根据iOS编程编写)
    Python_Day_05 计数器(counter),有序字典(OrderDict),默认字典(defaultdict),可命名元祖(namedtuple),双向队列(deque),单项队列(deuqe.Queue)
    iOS 视图与视图层次结构(内容根据iOS编程)
    Python_Day_04 set方法总结
    iOS通过ARC管理内存(内容根据iOS编程编写)
    Python_Day_03 list,dic,tuple方法总结
  • 原文地址:https://www.cnblogs.com/bangiao/p/13207344.html
Copyright © 2011-2022 走看看