我通過除錯ConcurrentLinkedQueue發現一個IDEA的小蟲子(bug), vscode復現, eclipse毫無問題
前言: 本渣渣想分析分析
Doug Lea
大佬對高併發程式碼編寫思路, 於是找到了我們今天的小主角ConcurrentLinkedQueue
進行鞭打, 說實話草稿我都打好了, 就差臨門一腳, 給踢折了
直接看問題, idea
在Debug
和非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); }
完美復現
第一反應我的問題
去原始碼裡看看怎麼回事. 但.......這...........
仔細看紅箭頭的地址, t
、p
、head
和tail
都是同一個地址, 看上面的程式碼發現全是tail
賦值給這三個變數的
而NEXT
原始碼
他的接收類是Node
, 接收欄位是next
, 接收欄位型別Node
看這原始碼的勢頭, NEXT
修改的是p
物件, 如果該物件的next
節點為null
, 則把newNode
設定到節點上, 此時p
物件指向的是tail
, 同時head
也是指向的tail
節點, 所以這句話執行完畢, head.next
和tail.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
堅挺住了