Java并发编程学习五:并发容器
一、HashMap为什么线程不安全?
HashMap是Map的主要实现类之一,但是它并不具备线程安全的特点。
以HashMap的put方法来看
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
//modCount++ 是一个复合操作
modCount++;
addEntry(hash, key, value, i);
return null;
}
且不去具体分析put方法的内部逻辑,单看代码中的modCount++操作,前面在对线程安全的学习部分,就提到过这个操作并不是原子操作,它的实际执行步骤有三步,每一步操作都有可能被打断。
除了非原子操作,HashMap还有什么原因导致它是线程不安全的呢?
1. 扩容时获取值可能为null
HashMap 本身默认的容量不是很大,如果不停地往 map 中添加新的数据,它便会在合适的时机进行扩容。而在扩容期间,它会新建一个新的空数组,并且用旧的项填充到这个新的数组中去。那么,在这个填充的过程中,如果有线程获取值,很可能会取到 null 值,而不是原来添加的值
public class HashMapNotSafe {
public static void main(String[] args) {
final Map<Integer, String> map = new HashMap<>();
final Integer targetKey = 0b1111_1111_1111_1111; // 65 535
final String targetValue = "v";
map.put(targetKey, targetValue);
new Thread(() -> {
IntStream.range(0, targetKey).forEach(key -> map.put(key, "someValue"));
}).start();
while (true) {
if (null == map.get(targetKey)) {
throw new RuntimeException("HashMap is not thread safe.");
}
}
}
}