HashMap多线程并发问题
HashMap并不是线程安全的,在多个线程put时,会形成key之间的死循环。当另外一个线程调用这个key时,get()方法会一直执行,致使线程积压,最终形成CPU满。算法
问题缘由分析
HashMap结构
HashMap经过一个数组table[]来存储key,当放入一个key时,经过hash算法计算key的下标,并存储在数组的table[i]处。若是table[]的尺寸很小,当放入多个key时,可能会出现下标相同,这样就会在table[i]处造成链表。这时,本来查找一个key须要耗时O(1),如今耗时变成了O(n),n为链表的长度。数组
所以,为了提升查找效率,当有新的key值存入时,会检查Hash表的大小是否超过设定的thredhold,超过的话就会增长Hash表的大小,这样子Hash表里的元素会从新计算一遍,这个过程叫作rehash。安全
正常的Rehash过程
假设原先hash的size是2,存放了三个元素a、b、c,都在table[1]这里,rehash后size为4。多线程
取出第一个key值a,计算hasn值为3,存放在table[3];并发
取出第二个key值b,计算hash值为1,存放在table[1];spa
取出第三个key值c,计算hash值为3,存放在table[3],与key值a造成链表,且c的next指向了a。线程
并发的Rehash过程
若是存在线程1和线程2,在rehash以前中,a、b、c在table[1]造成了链表,a的next指向了b,这时发生了put操做,两个线程同时进行了rehash。get
线程1在遍历Hash表元素中,取a.next时被挂起。同步
线程2继续完成了rehash操做,重组了链表,重组结束后,b.next指向了a。hash
线程1继续执行,a.next又指向了b,环形链表所以产生了。
这时,当咱们调用到同一链表的其余值时,就会出现死循环,线程一直会执行下去。
解决方案
HashTable
HashTable是同步的,对此对HashTable进行操做时,都会锁住整个结构。若是并发地修改,会抛出异常。
HashTable不支持在遍历时修改自身的元素,不然会抛出ConcurrentModificationException。
ConcurrentHashMap
ConcurrentHashMap的锁是细粒度的,将hash表分为16个桶(默认),每次须要时只会锁当前用到的桶。并且在读是不会锁表,彻底支持并发操做。只有在size()时会锁住整个表,所以ConcurrentHashMap并发时效率更高。
此外,ConcurrentHashMap则是在遍历迭代时发生改变不会抛出异常。