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

直接看问题, ideaDebug非Debug模式下运行结果不同, vscode复现, eclipse毫无鸭梨

怎么发现的问题?

从这段代码开始

  1. public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
  2. ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
  3. queue.add("zhazha");
  4. // 在下面这行下断点
  5. Field headField = queue.getClass().getDeclaredField("head");
  6. headField.setAccessible(true);
  7. Object head = headField.get(queue);
  8. Field itemField = queue.getClass().getDeclaredField("ITEM");
  9. itemField.setAccessible(true);
  10. VarHandle ITEM = (VarHandle) itemField.get(head);
  11. Object o = ITEM.get(head);
  12. System.out.println(o);
  13. }

你会发现一个神奇的现象, 如果我们下断点在Field headField = queue.getClass().getDeclaredField("head");这一行代码, 单步执行下来会发现System.out.println(o);打印出了zhazha, 但是如果不下断点, 直接运行打印null

为了防止是WARNING: An illegal reflective access operation has occurred警告的影响, 我改了改源码, 用unsafe获取试试

  1. private static Unsafe unsafe;
  2. static {
  3. Class<Unsafe> unsafeClass = Unsafe.class;
  4. Unsafe unsafe = null;
  5. try {
  6. Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
  7. unsafeField.setAccessible(true);
  8. ConcurrentLinkedQueueDemo.unsafe = (Unsafe) unsafeField.get(null);
  9. } catch (NoSuchFieldException | IllegalAccessException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
  14. ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
  15. queue.add("zhazha");
  16. // 在下面这行下断点
  17. long headOffset = unsafe.objectFieldOffset(queue.getClass().getDeclaredField("head"));
  18. Object head = unsafe.getObject(queue, headOffset);
  19. long itemOffset = unsafe.staticFieldOffset(ConcurrentLinkedQueue.class.getDeclaredField("ITEM"));
  20. Object base = unsafe.staticFieldBase(ConcurrentLinkedQueue.class.getDeclaredField("ITEM"));
  21. VarHandle ITEM = (VarHandle) unsafe.getObject(base, itemOffset);
  22. Object o = ITEM.get(head);
  23. System.out.println(o);
  24. }

完美复现

第一反应我的问题

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

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

NEXT源码

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

看这源码的势头, NEXT修改的是p对象, 如果该对象的next节点为null, 则把newNode设置到节点上, 此时p对象指向的是tail, 同时head也是指向的tail节点, 所以这句话执行完毕, head.nexttail.next同样都是newNode节点

但.....................这.....................

head节点被直接替换掉, tail保持不变

此时我的表情应该是这样

怀疑猫生

  1. private static Unsafe unsafe;
  2. static {
  3. Class<Unsafe> unsafeClass = Unsafe.class;
  4. Unsafe unsafe = null;
  5. try {
  6. Field unsafeField = unsafeClass.getDeclaredField("theUnsafe");
  7. unsafeField.setAccessible(true);
  8. ConcurrentLinkedQueueDemo.unsafe = (Unsafe) unsafeField.get(null);
  9. } catch (NoSuchFieldException | IllegalAccessException e) {
  10. e.printStackTrace();
  11. }
  12. }
  13. public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
  14. ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
  15. queue.add("zhazha");
  16. // 在这里下断点
  17. Class<? extends ConcurrentLinkedQueue> queueClass = queue.getClass();
  18. Object head = unsafe.getObject(queue, unsafe.objectFieldOffset(queueClass.getDeclaredField("head")));
  19. Field itemField = queueClass.getDeclaredField("ITEM");
  20. itemField.setAccessible(true);
  21. VarHandle ITEM = (VarHandle) itemField.get(queue);
  22. Object item = ITEM.get(head);
  23. System.out.println(item); // zhazha
  24. long itemOffset = unsafe.staticFieldOffset(queueClass.getDeclaredField("ITEM"));
  25. Object base = unsafe.staticFieldBase(queueClass.getDeclaredField("ITEM"));
  26. VarHandle ITEM2 = (VarHandle) unsafe.getObject(base, itemOffset);
  27. Object item2 = ITEM2.get(head);
  28. System.out.println(item2); // zhazha
  29. }

单步调试出来还是zhazha, 而且为了防止反射出了问题, 我同时用了Unsafe和反射两种方法

copy 源码添加自己的调试函数再次测试

得了得了, 放终极大招试试, copy ConcurrentLinkedQueue源码出来改成MyConcurrentLinkedQueue

offer方法添加几个输出

  1. public boolean offer(E e) {
  2. final Node<E> newNode = new Node<E>(Objects.requireNonNull(e));
  3. for (Node<E> t = tail, p = t; ; ) {
  4. Node<E> q = p.next;
  5. if (q == null) {
  6. if (NEXT.compareAndSet(p, null, newNode)) {
  7. System.out.println("this.head.item = " + this.head.item);
  8. System.out.println("this.tail.item = " + this.tail.item);
  9. System.out.println("this.head.next.item = " + this.head.next.item);
  10. System.out.println("this.tail.next.item = " + this.tail.next.item);
  11. if (p != t) {
  12. TAIL.weakCompareAndSet(this, t, newNode);
  13. }
  14. return true;
  15. }
  16. }
  17. else if (p == q) {
  18. p = (t != (t = tail)) ? t : head;
  19. }
  20. else {
  21. p = (p != t && t != (t = tail)) ? t : q;
  22. }
  23. }
  24. }

主函数就比较简单了直接

  1. public static void main(String[] args) {
  2. MyConcurrentLinkedQueue<String> queue = new MyConcurrentLinkedQueue<String>();
  3. queue.add("zhazha");
  4. }

直接在非Debug模式下运行, 发现打印出来的是

  1. this.head.item = null
  2. this.tail.item = null
  3. this.head.next.item = zhazha
  4. this.tail.next.item = zhazha
  5. Process finished with exit code 0

Debug模式下单步运行发现

  1. this.head.item = zhazha
  2. this.tail.item = null
  3. Exception in thread "main" java.lang.NullPointerException
  4. at com.zhazha.juc.MyConcurrentLinkedQueue.offer(MyConcurrentLinkedQueue.java:117)
  5. at com.zhazha.juc.MyConcurrentLinkedQueue.add(MyConcurrentLinkedQueue.java:67)
  6. at com.zhazha.juc.MyConcurrentLinkedQueueDemo.main(MyConcurrentLinkedQueueDemo.java:13)
  7. Process finished with exit code 1

纳尼?

不信邪的我在NEXT cas操作的前后增加了sleep方法, 以非Debug模式下运行

  1. this.head.item = null
  2. this.tail.item = null
  3. this.head.next.item = zhazha
  4. this.tail.next.item = zhazha

还是不一样

多环境IDE测试

放终极终极终极SVIP大招 ===> 放在eclipse上试试??? 或者vscode上???

在vscode上以Debug模式单步运行输出

  1. this.head.item = zhazha
  2. this.tail.item = null
  3. Exception in thread "main" java.lang.NullPointerException
  4. at MyConcurrentLinkedQueue.offer(MyConcurrentLinkedQueue.java:116)
  5. at MyConcurrentLinkedQueue.add(MyConcurrentLinkedQueue.java:66)
  6. at MyConcurrentLinkedQueueDemo.main(MyConcurrentLinkedQueueDemo.java:11)

非Debug模式直接输出

  1. this.head.item = null
  2. this.tail.item = null
  3. this.head.next.item = zhazha
  4. this.tail.next.item = zhazha

在eclipse上以Debug模式单步运行输出

  1. this.head.item = null
  2. this.tail.item = null
  3. this.head.next.item = zhazha
  4. this.tail.next.item = zhazha

非Debug运行输出

  1. this.head.item = null
  2. this.tail.item = null
  3. this.head.next.item = zhazha
  4. this.tail.next.item = zhazha

发现了没有? 还是我大eclipse坚挺住了

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

  1. 发现一个c++ vector sort的bug

    在开发中遇到一个非常诡异的问题:我用vector存储了一组数据,然后调用sort方法,利用自定义的排序函数进行排序,但是一直都会段错误,在排序函数中打印参加排序的值,发现有空值,而且每次都跟同一个数据 ...

  2. 踩坑,发现一个ShardingJdbc读写分离的BUG

    ShardingJdbc 怎么处理写完数据立即读的情况的呢? 写在前面 我本地使用了两个库来做写库(ds_0_master)和读库(ds_0_salve),两个库并没有配置主从. 下面我就使用库里的 ...

  3. 发现一个animate的小应用

    <script src="jquery-1.11.1.js"></script> <script> //animate() : //第一个参数 ...

  4. 调试 lvgl 的一个例子

    发现一个新的 vector graphic 的库,用 C 写的,效果丰富,接口简单,而且是 MIT License,所以想试一试.因为它支持 framebuffer,所以,在 linux 上先走一个. ...

  5. Entity Framework 更新失败,调试后发现是AsNoTracking的原因

    public override int SaveChanges() { var changedEntities = ChangeTracker.Entries().Where(e => e.St ...

  6. 从偶然的机会发现一个mysql特性到wooyun waf绕过题

    从偶然的机会发现一个mysql特性到wooyun waf绕过题 MayIKissYou | 2015-06-19 12:00 最近在测试的时候,偶然的机会发现了一个mysql的特性, 为啥是偶然的机会 ...

  7. 【轮子】发现一个效果丰富酷炫的Android动画库

    没有什么比发现一个好轮子更让人开心的了. 这个库分分钟提高交互体验 :AndroidViewAnimations 一张图说明一切 配置和使用也相当简单 GitHub地址

  8. 学习LINQ,发现一个好的工具。LINQPad!!

    今日学习LINQ,发现一个好的工具.LINQPad!! 此工具的好处在于,不需要在程序内执行,直接只用工具测试.然后代码通过即可,速度快,简洁方便. 可以生成其LINQ查询对应的lambda和SQL语 ...

  9. 发现一个挺好用的adb logcat工具

    其实是个Notepad++插件 直接贴地址: [http://sourceforge.net/projects/androidlogger/] ============================ ...

随机推荐

  1. Linux进阶之进程管理

    本节内容 1.进程管理 2.ps 3.uptime 4.top 5.ss -tnl------ lsof -i :22 一. 进程管理的概念 程序:二进制文件,静态 /bin/date,/usr/sb ...

  2. docker命令补全

    安装docker自带包: source /usr/share/bash-completion/completions/docker 缺少下面的包,TAB会报错 yum install -y bash- ...

  3. VMWare虚拟机显示模块“Disk”启动失败

    找到启动虚拟机的目录: 在此路径中找到.vmx文件,在文件中查找(Ctrl+F快速查找)vmci0.present,此时会看到"vmci0.present = "TRUE" ...

  4. python @staticmethod @classmethod self cls方法区别

    一直在用这些东西,但是又从来没有总结过,正好今日想起来就总结一下这些东西 @staticmethod 静态方法,名义上归属类管理,不能使用类变量和实例变量,类的工具包放在函数前,不能访问类属性和实例属 ...

  5. 《Java架构师的最佳实践》生产环境JVM调优之空间担保失败引起的FullGC

    1  问题现象 应用prod-xxx-k8s,在内存足够的情况下,仍然会产生偶发FullGC的问题. JVM配置如下: -Xmx8192m -Dhsf.server.max.poolsize=2500 ...

  6. .NET Worker Service 添加 Serilog 日志记录

    前面我们了解了 .NET Worker Service 的入门知识[1] 和 如何优雅退出 Worker Service [2],今天我们接着介绍一下如何为 Worker Service 添加 Ser ...

  7. mybatis运行出现org.apache.ibatis.binding.BindingException

    今天学习mybatis的第一天,发现用junit测试报出了次异常:org.apache.ibatis.binding.BindingException: Type interface cn.dzp.d ...

  8. Python+Selenium - windows安全中心的弹窗(账号登录)

    当出现如下图所示的 Windows安全中心弹窗,需要输入用户名和密码时 如何用Python+selenium跳过这个登录. 步骤: 1.在注册表中三个位置各添加两个东西:iexplore.exe 和 ...

  9. Django(48)drf请求模块源码分析

    前言 APIView中的dispatch是整个请求生命过程的核心方法,包含了请求模块,权限验证,异常模块和响应模块,我们先来介绍请求模块 请求模块:request对象 源码入口 APIView类中di ...

  10. C#-防止用户输入具有风险的敏感字符

    最近有涉及到要防止用户在网页文本框中输入具有风险的敏感字符所以特地编写了一套针对用户输入的字符进行安全过滤的一个方法,在后台接收到用户输入的字符后调用执行该方法即可完成过滤操作,主要使用正则来匹配并替 ...