我通过调试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坚挺住了
我通过调试ConcurrentLinkedQueue发现一个IDEA的小虫子(bug), vscode复现, eclipse毫无问题的更多相关文章
- 发现一个c++ vector sort的bug
在开发中遇到一个非常诡异的问题:我用vector存储了一组数据,然后调用sort方法,利用自定义的排序函数进行排序,但是一直都会段错误,在排序函数中打印参加排序的值,发现有空值,而且每次都跟同一个数据 ...
- 踩坑,发现一个ShardingJdbc读写分离的BUG
ShardingJdbc 怎么处理写完数据立即读的情况的呢? 写在前面 我本地使用了两个库来做写库(ds_0_master)和读库(ds_0_salve),两个库并没有配置主从. 下面我就使用库里的 ...
- 发现一个animate的小应用
<script src="jquery-1.11.1.js"></script> <script> //animate() : //第一个参数 ...
- 调试 lvgl 的一个例子
发现一个新的 vector graphic 的库,用 C 写的,效果丰富,接口简单,而且是 MIT License,所以想试一试.因为它支持 framebuffer,所以,在 linux 上先走一个. ...
- Entity Framework 更新失败,调试后发现是AsNoTracking的原因
public override int SaveChanges() { var changedEntities = ChangeTracker.Entries().Where(e => e.St ...
- 从偶然的机会发现一个mysql特性到wooyun waf绕过题
从偶然的机会发现一个mysql特性到wooyun waf绕过题 MayIKissYou | 2015-06-19 12:00 最近在测试的时候,偶然的机会发现了一个mysql的特性, 为啥是偶然的机会 ...
- 【轮子】发现一个效果丰富酷炫的Android动画库
没有什么比发现一个好轮子更让人开心的了. 这个库分分钟提高交互体验 :AndroidViewAnimations 一张图说明一切 配置和使用也相当简单 GitHub地址
- 学习LINQ,发现一个好的工具。LINQPad!!
今日学习LINQ,发现一个好的工具.LINQPad!! 此工具的好处在于,不需要在程序内执行,直接只用工具测试.然后代码通过即可,速度快,简洁方便. 可以生成其LINQ查询对应的lambda和SQL语 ...
- 发现一个挺好用的adb logcat工具
其实是个Notepad++插件 直接贴地址: [http://sourceforge.net/projects/androidlogger/] ============================ ...
随机推荐
- 010.kubernets的调度系统之daemonset
daemonset简单操作使用 Deployment 是 Kubernetes 中用于处理无状态服务的资源,而 StatefulSet 是用于支持有状态服务的资源,这两种不同的资源从状态的角度对服务进 ...
- STM32 SWD下载口无法下载的原因和解决办法
1.SWD的下载口在程序中被禁用,IO口被设置为普通IO口 2.芯片被锁,原因有可能是程序执行了不正确的访问导致芯片被锁 3.供电不正常 4.SWD烧了 解锁原因: 在下载程序的时候有时候会发生错误导 ...
- flink-cdc读取postgres报异常,没有发布表
异常信息 must be superuser to create FOR ALL TABLES publication 必须是超级用户才能为所有发布表创建 网上搜索了一天,都毫无头绪,后面搜索到了一个 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十五)——集中式接口文档实现
之前有小伙伴在评论区留言说如何集成swagger,最开始没有想透给了对方一个似是而非的回答.实际上后来下来想了一下,用.NET5 提供的Source Generator其实可以很方便的实现接口集成.今 ...
- MarkDown笔记二
表格 列1|列2|列3 --|--|-- 内容1|内容2|内容3 下例冒号在左为左对齐(默认),在右为右对齐,在俩侧为居中对齐 | 左对齐 | 右对齐 | 居中对齐 | | :-----| ----: ...
- Ubuntu下安装与卸载Nginx
1.Ubuntu下安装Nginx比较简单 敲入下列命令即可: sudo apt-get update sudo apt-get install nginx 2.Ubuntu下卸载,稍不注意就会入坑 s ...
- 使用 JavaScript 将 HTML 转换为 PDF
使用 JavaScript 将 HTML 转换为 PDF 更多相关学习资料参见http://www.pdfdownload.cn/b/ba_index.php 在本文中,我们将了解如何在浏览器(即完全 ...
- Proteus中包含的传感器类型(Transducers)
1. 传感器列表 2. 部分传感器的测量电路 (1)光照传感器,搭采样电阻,测电压输出. (2)距离传感器,带采样电阻,测电压输出. (3)粉尘传感器,测PWM脉宽 其余传感器多为总线类型的传感器,各 ...
- ALD和CVD晶体管薄膜技术
ALD和CVD晶体管薄膜技术 现代微处理器内的晶体管非常微小,晶体管中的一些关键薄膜层甚至只有几个原子的厚度,光是英文句点的大小就够容纳一百万个晶体管还绰绰有余.ALD 是使这些极细微结构越来越普遍的 ...
- MegEngine亚线性显存优化
MegEngine亚线性显存优化 MegEngine经过工程扩展和优化,发展出一套行之有效的加强版亚线性显存优化技术,既可在计算存储资源受限的条件下,轻松训练更深的模型,又可使用更大batch siz ...