1、为什么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数组(默认大小为16),所有hash值相同(即产生了冲突)的key会存储到同一链表里。
(1)假设正好存在两个put的key发生了碰撞(hash值相同),那么根据HashMap的实现,这两个key会添加到数组的同一个位置,这样就会发生其中一个key的value被覆盖。
(2)如果多个线程同时检测到元素的个数超过(数组大小*负载因子),这样就会发生多个进程同时对Node数组进行扩容,都在重新计算元素位置以及复制数据,但是最终只有一个线程扩容后的数组会赋给table,也就是说其他线程的都会丢失,并且各自线程put的数据也丢失。
2、如何线程安全的使用HashMap?
.HashTable
.ConcurrentHashMap
.Synchronized Map
(1)//Hashtable
Map<String, String> hashtable = new Hashtable<>();
HashTable源码中使用synchronized来保证线程安全的,所以当一个线程访问HashTable的同步方法时,其他线程也要访问就会被阻塞。
(2)//synchronizedMap
// synchronizedMap方法
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
// SynchronizedMap类
private static class SynchronizedMap<K,V>
implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
private final Map<K,V> m; // Backing Map
final Object mutex; // Object on which to synchronize
SynchronizedMap(Map<K,V> m) {
this.m = Objects.requireNonNull(m);
mutex = this;
}
SynchronizedMap(Map<K,V> m, Object mutex) {
this.m = m;
this.mutex = mutex;
}
public int size() {
synchronized (mutex) {return m.size();}
}
public boolean isEmpty() {
synchronized (mutex) {return m.isEmpty();}
}
public boolean containsKey(Object key) {
synchronized (mutex) {return m.containsKey(key);}
}
public boolean containsValue(Object value) {
synchronized (mutex) {return m.containsValue(value);}
}
public V get(Object key) {
synchronized (mutex) {return m.get(key);}
}
public V put(K key, V value) {
synchronized (mutex) {return m.put(key, value);}
}
public V remove(Object key) {
synchronized (mutex) {return m.remove(key);}
}
// 省略其他方法
}
调用synchronizedMap()方法后会返回一个SynchronizedMap类的对象,而在SynchronizedMao类中使用了synchronized同步关键字来保证对Map的操作是安全的。(synchronized
一个时间内只能有一个线程得到执行。另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块。)
(3)//ConcurrentHashMap
Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();