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];

  取出第三个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。

  线程1在遍历Hash表元素中,取a.next时被挂起。

  线程2继续完成了rehash操作,重组了链表,重组结束后,b.next指向了a。

  线程1继续执行,a.next又指向了b,环形链表因此产生了。

  这时,当我们调用到同一链表的其他值时,就会出现死循环,线程一直会执行下去。

解决方案

HashTable

  HashTable是同步的,对此对HashTable进行操作时,都会锁住整个结构。如果并发地修改,会抛出异常。

  HashTable不支持在遍历时修改自身的元素,否则会抛出ConcurrentModificationException。

ConcurrentHashMap

  ConcurrentHashMap的锁是细粒度的,将hash表分为16个桶(默认),每次需要时只会锁当前用到的桶。而且在读是不会锁表,完全支持并发操作。只有在size()时会锁住整个表,因此ConcurrentHashMap并发时效率更高。

  此外,ConcurrentHashMap则是在遍历迭代时发生改变不会抛出异常。

  

猜你喜欢

转载自www.cnblogs.com/snowcity1231/p/10480026.html