不安全。
翻到今年前写的笔记,不过当时估计看的是1.6版本的,有一些内容如果已经发生变化,请留言指正。(加粗的部分都是知识点)
非线程安全
并发场景中如果不保持足够的同步,可能会在执行HashMap.get时进入死循环。
高并发场景中,使用ConcurrentHashMap;采用数组方式存储key.value构成的Entry对象,无容量限制;基于key hash寻找Entry对象存储到数组中的位置,对于hash冲突采用链表方式解决;插入元素时可能要扩大数组的容量,需要重新计算hash,并复制新对象到数组中。
HashMap
- 创建:HashMap():
默认是创建loadFactor = 0.75 ,threshold = 12 ,大小为16的Entry对象数组。
- 增加:put(Object key , Object value)
key = null ,遍历,找到则将新的value赋值;如果没有找到则增减一个Entry对象,key为null,value为传入对象,next指向当前数组的第一个对象;
如果当前数组使用大小 >= threshold,则将数组扩大为当前大小的两倍,扩大时对当前Entry对象数组中的元素重新hash,并填充数组,重新设置threshold值。
-
获取单个对象:get(Object key)
!=null时,对key进行按位与操作,找到对应的存储位置,找到此位置对应的Entry对象,基于next属性遍历,需要到hash值相等,且key值相等并或equals的Entry对象,返回其value,未找到返回null。
-
删除:remove( Object key)
与get类似
-
判断对象是否存在:containsKey( Object key )
调用getEntry方法完成,过程与get过程基本相同。
-
遍历:keySet()
遍历时,无法保证顺序
希望我的解答能够帮助到你!
hashMap是否线程安全基本上是在每次面试都会问的了,而99%的JAVA程序员都知道hashMap是非线程安全的,不过知道其底层原因的应该不多,下面来说下为什么是线程不安全的!
我们都知道,hashMap是一种在开发中最常用的数据结构(key-value型),因为它很快(O(1)常数级别的查询),在存储的时候通过计算key的hash值,将value存在对应的桶里(数组的一个元素),然后通过头插法插入桶中的单向链表里,如下图所示:
这个过程是线程安全的,因为就算是发生了线程同享资源,无非就是插入的数据顺序问题而已,无伤大雅,但是我们都知道,hashMap为了防止数据查询过慢(如果单链表中的数据过大,相当于O(1)常数级别的性能下降为O(N)线性级别),采取了自动扩容的方式,一旦存储数据的大小size超过了总容量的0.75(装载因子),就发生自动扩容,安全问题也就随之诞生了!
hashMap怎么实现扩容呢?一旦需要扩容的时候,会新建一个两倍容量的hashMap,并把原来的元素重新做hash存入新的hash数组,底层源码使用的resize方法:
在resize方法中需要调用transfer方法,方法很简单,无非就是遍历老hash数组,然后重新放入新的hash数组中,如下:
当thread1执行到Entry<k,v> next =e.next的时候,把原来的key7指向key3,变为了key3指向key7,因为java是抢占式线程,此时thread2开始执行,不过在线程2中的快照中,还是key7指向key3(但实质key3已经指向了key7)!
经过线程2的处理完成,线程继续处理,这个时候key3指向key7,反过来key7也指向key3,这时候单链表变成了环形链表:
等到查询方法查找到这个hash数组的时候,查询出现了死循环,永远卡死在这,CPU跑满。
所以hashMap是非线程安全的,相对应的hashTable拥有着和hashMap类似的结构,但是因为hashTable中的所有方法都加了锁(synchonize),所以在多线程处理中,应该是用hashTable来换取数据的安全性!
原创文章,作者:普尔小编,如若转载,请注明出处:http://www.puerpx.cn/pxwd/8447.html