前几天,有位小伙伴向我反馈,在维护代码过程中,出现了一个莫名其妙的问题。明明上线之后程序跑得还好好的,可程序上线运行一段时间之后,所有,代码没有做任何修改,发 cxccccc现运行结果和期望值恰好相反。因为涉及到金额造成了比较大的损失,最后,这位小伙伴还被公司辞退了,大家可以来评论一下,这位小伙伴背的这个锅值不值?

1、业务场景

大家来看,他的代码大致是这样写的:

java两个integer比较大小:两个Integer对象比较大小(1)

一般情况下,a和b都输入100的时候,返回为true,但当a和b都输入1000的时候,返回为false。按照正常逻辑理解,100 等于等于 100,那1000 为什么不等于等于1000 呢?这位同学,百思不得其解。于是,这位同学,还特意写了一段测试代码

java两个integer比较大小:两个Integer对象比较大小(2)

这到底是什么原因呢?我们对照Integer的源码来进行分析:

2、源码分析

我摘取了Integer的源码片段,它有一个valueOf()的方法:

java两个integer比较大小:两个Integer对象比较大小(3)

我们可以看到,Integer源码中的valueOf()方法做了一个条件判断,其中 IntegerCache.low的值为-128,

java两个integer比较大小:两个Integer对象比较大小(4)

IntegerCache.high的值为127。

java两个integer比较大小:两个Integer对象比较大小(5)

也就是说如果目标值在-128~127之间,会直接从cache数组中取值,否则就会新建对象。

这里又有人会问了,那为什么默认是-128 - 127,怎么不是-200 - 200或者是其他值呢?那JDK为何要这样做呢?

java两个integer比较大小:两个Integer对象比较大小(6)

在Java API 中是这样解释的:

Returns an Integer instance representing the specified int value. If a new Integer instance is not required, this method should generally be used in preference to the constructor Integer(int), as this method is likely to yield significantly better space and time performance by caching frequently requested values. This method will always cache values in the range -128 to 127, inclusive, and may cache other values outside of this range

大致意思是:

-128~127的数据在int范围内是使用最频繁的,为了减少频繁创建对象带来的内存消耗,这里其实是用到了享元模式,以提高空间和时间性能。

在JDK中,这样的应用不止int,我给小伙伴们整理了一个表格,表格中的其他类型也都应用了享元模式,也就是说对数值做了缓存,只是缓存的范围不一样,具体如表中所示:

java两个integer比较大小:两个Integer对象比较大小(7)

我需要这张表的小伙伴可以私信我,以上就是关于Integer对象比较大小的分析,听懂了的小伙伴,请关注点个赞,下次不迷路。

我是被编程耽误的文艺Tom,如果大家还有其他疑问,请在评论区留言。如果本次面试解析对你有帮助,请动动手指一键三连分享给更多的人。关注我,面试不再难!

14.为什么HashMap会产生死循环?

HashMap死循环是一个比较常见、也是比较经典的面试题,在大厂的面试中也经常被问到。HashMap的死循环问题只在JDK1.7版本中会出现,主要是HashMap自身的工作机制,再加上并发操作,从而导致出现死循环。JDK1.8以后,官方彻底解决了这个问题。

1、数据插入原理

在分析原因之前,我先带大家了解一下JDK1.7中HashMap插入数据的原理,来看动画演示:

java两个integer比较大小:两个Integer对象比较大小(8)

由于JDK 1.7中HashMap的底层存储结构采用的是数组 加 链表的方式。

java两个integer比较大小:两个Integer对象比较大小(9)

而HashMap在数据插入时又采用的是头插法,也就是说新插入的数据会从链表的头节点进行插入。

java两个integer比较大小:两个Integer对象比较大小(10)

因此,HashMap正常情况下的扩容就是是这样一个过程。我们来看,旧HashMap的节点会依次转移到新的HashMap中,旧HashMap转移链表元素的顺序是A、B、C,而新HashMap使用的是头插法插入,所以,扩容完成后最终在新HashMap中链表元素的顺序是C、B、A。

2、导致死循环的原因

接下来,我通过动画演示的方式,带大家彻底理解造成HashMap死循环的原因。我们按以下三个步骤来还原并发场景下HashMap扩容导致的死循环问题:

java两个integer比较大小:两个Integer对象比较大小(11)

第一步:线程启动,有线程T1和线程T2都准备对HashMap进行扩容操作, 此时T1和T2指向的都是链表的头节点A,而T1和T2的下一个节点分别是T1.next和T2.next,它们都指向B节点。

java两个integer比较大小:两个Integer对象比较大小(12)

第二步:开始扩容,这时候,假设线程T2的时间片用完,进入了休眠状态,而线程T1开始执行扩容操作,一直到线程T1扩容完成后,线程T2才被唤醒。

java两个integer比较大小:两个Integer对象比较大小(13)

T1完成扩容之后的场景就变成动画所示的这样。

java两个integer比较大小:两个Integer对象比较大小(14)

因为HashMap扩容采用的是头插法,线程T1执行之后,链表中的节点顺序发生了改变。但线程T2对于发生的一切还是不可知的,所以它指向的节点引用依然没变。如图所示,T2指向的是A节点,T2.next指向的是B节点。

java两个integer比较大小:两个Integer对象比较大小(15)

当线程T1执行完成之后,线程T2恢复执行时,死循环就发生了。

java两个integer比较大小:两个Integer对象比较大小(16)

因为T1执行完扩容之后,B节点的下一个节点是A,而T2线程指向的首节点是A,第二个节点是B,这个顺序刚好和T1扩容之前的节点顺序是相反的。T1执行完之后的顺序是B到A,而T2的顺序是A到B,这样A节点和B节点就形成了死循环。

3、解决方案

避免HashMap发生死循环的常用解决方案有三个:1)、使用线程安全的ConcurrentHashMap替代HashMap,个人推荐使用此方案。

2)、使用线程安全的容器Hashtable替代,但它性能较低,不建议使用。

3)、使用synchronized或Lock加锁之后,再进行操作,相当于多线程排队执行,也会影响性能,不建议使用。

4、总结

HashMap死循环只发生在JDK1.7版本中,主要原因是JDK1.7中的HashMap,在头插法 加 链表 加 多线程并发 加 扩容这几个情形累加到一起就会形成死循环。多线程环境下建议采用ConcurrentHashMap替代。在JDK1.8中,HashMap改成了尾插法,解决了链表死循环的问题。

以上就是关于HashMap死循环原因的分析,听懂的小伙伴,关注点个赞,下次不迷路。

S信【Tom】或【666】即可免费领取需要更多干货内容,还有海量面试资料,只弹干货不惨水!

本文为“Tom弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom弹架构 』可获取更多技术干货!

关注微信公众号『 Tom弹架构 』可获取更多技术干货!往期视频已经整理成文档形式,需要的小伙伴点个关注,搜索下方名片!我是被编程耽误的文艺Tom,

java两个integer比较大小:两个Integer对象比较大小(17)

,