为什么HashMap是不安全的
HashMap的线程不安全是由于它本身的机制造成的。HashMap的存储结构是由数组+单链表组成的。
transient Node<K,V>[] table;
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
HashMap的内存存储使用了Node,Node中有一个属性为next,相当于是一个链表,所有hash值相同的key会存储到一个链表里。
PS:在Java 8中如果hash值相同的key数量大于指定值(默认是8)时使用平衡树来代替链表,这会将get()方法的性能从O(n)提高到O(logn)。
HashMap的Node数组默认大小为16,默认负载因子为0.75。如果当前数据长度大于当前最大容量*负载因子时,就会执行扩容resize(),扩容大小为之前的2倍。
HashMap在并发执行put操作时会引起死循环,导致CPU利用率接近100%。因为多线程会导致HashMap的Node链表形成环形数据结构,一旦形成环形数据结构,Node的next节点永远不为空,就会在获取Node时产生死循环。
如何使用线程安全的HashMap
Hashtable
Hashtable内部使用了synchronized来保证线程安全,效率较低。
ConcurrentHashMap
ConcurrentHashMap引入segment的概念。Segment在实现上继承了ReentrantLock。
Java8中摒弃了segment了,使用CAS的算法。
SynchronizedMap
Collections.getSynchronizedMap()返回一个SynchronizedMap()对象,使用synchronized关键字来保证对Map的操作是多线程安全的。
效率
ConcurrentHashMap > (SynchronizedMap or HashTable)