zoukankan      html  css  js  c++  java
  • Leader:这样的 Bug 你也写的出来???

    Hello~各位读者新年好!不知道大家春节假期是否已延长,小黑哥刚接到通知,假期延长到 2 月 2 号,另外回去之后需要在家办公,自行隔离两周。还没试过在家办公,小黑哥就怕到时候生物钟还没调整过来,一觉睡醒已经是下午了。。。

    前言

    春节假期,还躺在床上小黑哥,收到对账系统的一条预警短信,提示当前系统资金核对存在问题。关于资金的问题,都是大问题,小黑哥连忙拔出电脑,连上 VPN,登录生产环境的查看相关日志。

    通过日志,很快小黑哥定位到相关代码。

    有的同学可能一下子就能看出这里的问题, Long 对象采用 !=进行比较,这真是一个低级 Bug。幸好 Leader 还不知道,赶紧悄悄修复一下。

    现在回想小黑哥当初写这段代码的时候,误以为两个 Long 对象比较将会进行自动拆箱,转变为两个基本数值类型比较。

    下面开始复习一下 Java 自动装箱与拆箱机制。

    自动装箱与拆箱机制

    自动装箱(Autoboxing),是 JDK5 新增的一种语法糖,将会在代码编译时自动将原始类型转换为其对应的对象包装器类。例如将 int 转换为 Integerdouble 转换为 Double。如果转换结果相反,我们就将其称为拆箱。

    下面是一个自动装箱的例子:

    上面代码 li.add(i) 就发生自动装箱,将基本数据类型 long 转换为其包装类 Long

    查看这段代码对应的字节码。

    图上黄线标注的字节码对应的代码为 li.add(i)。从这我们可以看到 long 类型的自动装箱实际上调用 Long#valueOf 方法。所以编译器运行时将之前的代码转换为下面的代码

    接下来我们来看一个自动拆箱的例子:

    由于 Long 包装类对象不能用于求模(%)以及 +=,所以这段代码将会发生自动拆箱,将 Long 转化为 long 类型。相应的这里我们也看下其编译之后的字节码。

    这里的字节码比之前的复杂很多,这里主要关注黄线部分。可以看到这里调用 Long#longValueLong 对象转为 long 类型。所以自动拆箱这个例子,最后编译器生成字节码等同于以下代码:

    Java 规定的 8 种数据类型都有其对应包装类,这些都可以进行相应的自动装箱和拆箱。

    这里小结一下:

    1. 自动装箱机制是通过调用包装器类 valueOf 实现
    2. 自动拆箱机制通过调用包装器类的相应的 **Value ,如 longValue, intValue 实现

    Cache 陷阱

    自动装箱和拆箱概念说起其实挺简单的,但是如果使用不当可能就会踩坑。

    我们来看一段代码:

    如果你对上面的结果的不是很清楚,恭喜你,暖男小黑哥帮你排雷了。

    上面输出结果为:

    true
    false
    

    这里输出结果之所以为这样,主要与 LongCache 有关。自动装箱进制将会调用 Long#valueOf,其源码如下:

    只要数值范围位于 [-128,127] 之间,valueOf 就会返回 LongCache.cache 这个数组中的值。所以 aLong/bLong其实是同一个对象,cLong/dLong 是两个不用对象的。

    Long#valueOf 这个方法通过 Cache 减少创建对象的数量,提高相应的空间以及时间性能。

    至于为什么缓存 [-128,127] 之间的数字,而没有缓存更多的值,甚至缓存所有的值?

    这是因为 Long 范围为[-263,263],总共 2^64 ,这么多对象实例,显然不宜全部缓存。至于 [-128,127] 之间数字 JDK 设计者认为这些数字使用频率高,个人认为这是一个经验值。

    另外这个 cache可能会导致另外一个问题:锁共用。

    上面代码看起来 A 类使用 along 这个对象锁,而 B 类使用 blong 这个对象锁,看起来两个风马牛不相及,但是实际上两个对象由于 Cache 机制,导致其实际上使用同一个对象,AB 共用同一把锁。

    除了 Long#valueOf 方法中存在 Cache 以外,ByteShortIntegerCharacter 也存在相应的 Cache 值。其中 Integer 对象可以通过 -XX:AutoBoxCacheMax=<size> 改变 Cache 初始范围。

    技术总结

    通过自动装箱与拆箱机制,大大减少了数据类型转化的代码,但是同时带来一些隐藏的问题。这里我们需要记住:

    1. 所有对象之间不能使用 == 比较,需要使用 equals 代替,推荐使用 JDK7 提供的 Objects#equals 方法,该方法可以有效避免比较过程空指针问题
    2. 基本数据类型的包装类不适合当做锁对象

    各位读者朋友们,请牢记这两个总结,不要踩中这两个坑哦。另外推荐大家安装一下 FindBugs 这个插件,通过这个插件扫描代码,可以找出代码中的相关缺陷。上面两个问题,通过 FindBugs都能扫描出来,简直神器。

    对象比较问题:

    锁问题

    帮助文档

    1. Autoboxing and Unboxing『https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html』
    2. 极客时间-『Java并发编程实战』专栏
  • 相关阅读:
    _ 下划线 Underscores __init__
    Page not found (404) 不被Django的exception中间件捕捉 中间件
    从装修儿童房时的门锁说起
    欧拉定理 费马小定理的推广
    线性运算 非线性运算
    Optimistic concurrency control 死锁 悲观锁 乐观锁 自旋锁
    Avoiding Full Table Scans
    批量的单向的ssh 认证
    批量的单向的ssh 认证
    Corrupted MAC on input at /usr/local/perl/lib/site_perl/5.22.1/x86_64-linux/Net/SSH/Perl/Packet.pm l
  • 原文地址:https://www.cnblogs.com/goodAndyxublog/p/12238141.html
Copyright © 2011-2022 走看看