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


xixi和heihei
3 声望1 粉丝