JDK 1.8
采用尾插法解决了JDK 1.7
(出现Hash
冲突采用的头插法)中的并发扩容导致的循环链表问题。
JDK 1.8
下的HashMap
并发问题主要是由于多线程put
时,Hash
桶头节点元素值被覆盖的问题。
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素 // 当多个线程进入这里时,会有并发问题导致修改时,可能能会覆盖其他线程的修改: tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; // 多线程情况下变量不安全: if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
调用putVal
方法时,会判断是否出现hash
碰撞,假设两个线程A
、线程B
都在进行put
操作,并且hash
函数计算出的插入下标是相同的,当线程A
执行完第六行代码后由于时间片耗尽导致被挂起,而线程B
得到时间片后在该下标处插入了元素,完成了正常的插入。然后线程A获得时间片,由于之前已经进行了hash
碰撞的判断,所有此时不会再进行判断,而是直接进行插入,这就导致了线程B
插入的数据被线程A
覆盖了,从而线程不安全。
除此之前,还有就是代码的第38行处有个++size
,我们这样想,还是线程A
、线程B
,这两个线程同时进行put
操作时,假设当前HashMap
的zise
大小为10
,当线程A
执行到第38行代码时,从主内存中获得size
的值为10后准备进行+1
操作,但是由于时间片耗尽只好让出CPU
,线程B
快乐的拿到CPU
还是从主内存中拿到size的值10进行+1
操作,完成了put
操作并将size=11
写回主内存,然后线程A
再次拿到CPU
并继续执行(此时size
的值仍为10),当执行完put
操作后,还是将size=11
写回内存,此时,线程A
、线程B
都执行了一次put
操作,但是size
的值只增加了1
,所有说还是由于数据覆盖又导致了线程不安全。
Hashtable
:synchronized
关键字保证线程安全- 强化
HashMap
:
使用Collections
工具类的synchronizedMap
方法,将HashMap
改造为一个线程安全的Map
。
Collections.synchronizedMap(new HashMap<String,String>());
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) { return new SynchronizedMap<>(m); }
这个方法的实现原理是对HashMap
原来的方法进一步包装,多了synchronized
关键字,和Hashtable
原理一样。
ConcurrentHashMap
:并发安全的集合