HashMap多线程并发问题

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则是在遍历迭代时发生改变不会抛出异常。

相关文章
相关标签/搜索