并发下HashMap为什么不是线程安全的?
2018-01-12 10:56
169 查看
在知乎上看到的,觉得说的很清楚,所以就转载过来做一下记录。具体的出处可以看图片的水印。
首先看下HashMap的工作原理,我们回顾一下HashMap的结构:
HashMap的结构就是哈希表,底层是一个数组,这个数组中尽可能地分散所有的key,通过key的hash值得到数组下标,然后把entry插到该数组,假如有两个不同的key被分到相同的下标,也就是哈希冲突,那么该数组在该下标下就会形成链表。
为了减少冲突,我们需要时刻留意当前的size是否太大,检查是否需要扩容,一旦超过设定的threshold,那么就要重新增大数组尺寸,此时所有元素都需要重新计算应该放置的下标。
扩容、rehash
上面为扩容的方法,可以看到,实际上扩容就是新建了一个数组,同时调用了transfer方法,给新数组赋值后覆盖掉原来的table。那我们来看看transfer里面发生了什么:
从代码上可以看出,原来链表的元素有可能已经不在原来的数组上,也就是元素都被重新排到数组上了。
图解rehash
扩容前的结构:
在transfer方法中,对原来的table进行遍历重排序,我们来模拟一下这个过程。重新排序的关键在于,数组长度变了以后,重新计算得出的下标,这与哈希算法有关,为了便于理解,我们简单地认为本HashMap的哈希算法为用key 去mod数组的长度,这也是理解transfer如何重新排列链表的关键。
这是第一次循环后的结果:
第二步:
继续遍历,最终结果如下
以上就是在单线程下,重新排列后的链表。
多线程下的rehash
我们从上文看到,单线程下的rehash是完全没有问题的,那么在多线程下会出现什么问题呢?我们仍然用清晰明了的图来做演示
首先,我们假设有两个进程同时进行put操作,且让HashMap进行扩容,同时进入transfer方法
此时的状态如下:
然后线程2完成了transfer方法,如下
此时,又回到线程1,但此时,完成第一次循环过程:
继续进行下一次循环:
然后e = next = key(3),再继续下一次循环:
当执行完:
e.next = newTable[3];
newTable[3] = e;
时,至此,循环链表形成了。
另外大家也可以看小灰漫画中的一篇文章,写的也很清楚: https://mp.weixin.qq.com/s?__biz=MzI2NjA3NTc4Ng==&mid=2652079766&idx=1&sn=879783e0b0ebf11bf1a5767933d4e61f&chksm=f1748d73c6030465fe6b9b3fa7fc816d4704c91bfe46cb287aefccee459153d3287172d91d23&scene=21#wechat_redirect
首先看下HashMap的工作原理,我们回顾一下HashMap的结构:
HashMap的结构就是哈希表,底层是一个数组,这个数组中尽可能地分散所有的key,通过key的hash值得到数组下标,然后把entry插到该数组,假如有两个不同的key被分到相同的下标,也就是哈希冲突,那么该数组在该下标下就会形成链表。
为了减少冲突,我们需要时刻留意当前的size是否太大,检查是否需要扩容,一旦超过设定的threshold,那么就要重新增大数组尺寸,此时所有元素都需要重新计算应该放置的下标。
扩容、rehash
上面为扩容的方法,可以看到,实际上扩容就是新建了一个数组,同时调用了transfer方法,给新数组赋值后覆盖掉原来的table。那我们来看看transfer里面发生了什么:
从代码上可以看出,原来链表的元素有可能已经不在原来的数组上,也就是元素都被重新排到数组上了。
图解rehash
扩容前的结构:
在transfer方法中,对原来的table进行遍历重排序,我们来模拟一下这个过程。重新排序的关键在于,数组长度变了以后,重新计算得出的下标,这与哈希算法有关,为了便于理解,我们简单地认为本HashMap的哈希算法为用key 去mod数组的长度,这也是理解transfer如何重新排列链表的关键。
这是第一次循环后的结果:
第二步:
继续遍历,最终结果如下
以上就是在单线程下,重新排列后的链表。
多线程下的rehash
我们从上文看到,单线程下的rehash是完全没有问题的,那么在多线程下会出现什么问题呢?我们仍然用清晰明了的图来做演示
首先,我们假设有两个进程同时进行put操作,且让HashMap进行扩容,同时进入transfer方法
此时的状态如下:
然后线程2完成了transfer方法,如下
此时,又回到线程1,但此时,完成第一次循环过程:
继续进行下一次循环:
然后e = next = key(3),再继续下一次循环:
当执行完:
e.next = newTable[3];
newTable[3] = e;
时,至此,循环链表形成了。
另外大家也可以看小灰漫画中的一篇文章,写的也很清楚: https://mp.weixin.qq.com/s?__biz=MzI2NjA3NTc4Ng==&mid=2652079766&idx=1&sn=879783e0b0ebf11bf1a5767933d4e61f&chksm=f1748d73c6030465fe6b9b3fa7fc816d4704c91bfe46cb287aefccee459153d3287172d91d23&scene=21#wechat_redirect
相关文章推荐
- 牛客网Java刷题知识点之为什么HashMap不支持线程的同步,不是线程安全的?如何实现HashMap的同步?
- 为什么常规HashMap不是线程安全的?
- 为什么Java中的long与double不是线程安全的?
- 为什么说android UI操作不是线程安全的
- Map的new为什么是Map map=new HashMap();而不是new Map();?因为map是个接口,而接口不能被实例化!HashMap是map接口的实现类!
- Android为什么要设计出Bundle而不是直接使用HashMap来进行数据传递?
- 为什么hashmap是非线程安全的?
- 并发的HashMap为什么会引起死循环?
- 【java并发】造成HashMap非线程安全的原因
- 并发的HashMap为什么会引起死循环
- 为什么Java中的HashMap<K, V>的get函数是get(Object key),而不是get(K key)?
- 对于Hibernate的openSession方法为什么不是线程安全的源码理解
- HashMap为什么线程不安全?浅析高并发情况下的HashMap
- 为什么说android UI操作不是线程安全的
- 并发的HashMap为什么会引起死循环?
- 为什么说android UI操作不是线程安全的 分类: Android 2014-09-23 21:08 1357人阅读 评论(0) 收藏 举报 目录(?)[+] 可能在非UI线程中刷新界面的时候,U
- Android为什么要设计出Bundle而不是直接使用HashMap来进行数据传递 Android内存优化(使用SparseArray和ArrayMap代替HashMap)
- Android为什么要设计出Bundle而不是直接使用HashMap来进行数据传递
- Android为什么要设计出Bundle而不是直接使用HashMap来进行数据传递?
- 为什么要使用ConcurrentHashMap而不是HashMap