我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子bug-eclipse毫无问题

8次阅读

共计 5697 个字符,预计需要花费 15 分钟才能阅读完成。

前言: 本渣渣想分析分析 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 坚挺住了

正文完
 0