前言
前面的博客已经分析了ArrayList线程不安全的原因,以及解决方案,感兴趣的可以参考这里:ArrayList线程安全问题详解。
现在这边主要介绍的是Set、Map的线程安全问题
Set
Set最常用的就是HashSet类型,但是HashSet 是线程不安全的类型,不能用于并发环境。
如下这个例子,执行后会抛出java.util.ConcurrentModificationException的异常
public static void main( String[] args ) throws InterruptedException {
Set<String> set = new HashSet<>();
for (int i = 1; i <= 30; i++){
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(set);
}).start();
};
}
源码分析
private transient HashMap<E,Object> map;
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- HashSet的底层是HashMap,Key就是我们添加的数据,Value是个常量Object对象
- 既然如此,HashSet的线程不安全就是HashMap产生的,待会下面有介绍HashMap,这边先跳过
解决方案
- 使用CopyOnWriteArraySet初始化即可
Set<String> set = new CopyOnWriteArraySet<>();
- 使用Collections.synchronizedSet初始化
Set<String> set = Collections.synchronizedSet(new HashSet<>());
Map
Map最常用的就是HashMap类型,HashMap也是线程不安全的类型,也不能用于并发环境。
如下这个例子,执行后同样会抛出java.util.ConcurrentModificationException的异常
public static void main( String[] args ) throws InterruptedException {
Map<String, String> map = new HashMap<>();
for (int i = 1; i <= 30; i++){
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
}).start();
};
}
异常原因
- HashMap.put方法内部都是没有加锁的操作,所以并发操作很多属性都会乱掉。
- 由于HashMap源码相对繁琐一点,所以这里不贴出来了,下一篇文章会专门进行HashMap源码解析
解决方案:采用线程安全的Map类
- 使用ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
- 使用Collections.synchronizedMap
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());