并发编程【ArrayList、HashSet、HashMap线程不安全】
一、前言
在学习集合时,我们就已经知道了 ArrayList 是线程不安全的。
那能不能通过代码来体现呢?
因此,本文将演示 ArrayList 在多线程环境下的线程不安全的问题,并总结常用的解决办法。
二、代码实现
public class AboutList {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for(int i=1; i<=30; i++){
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
当运行时,会出现如下问题:
Exception in thread "24" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.zuobiao.juc.AboutList.lambda$main$0(AboutList.java:17)
at java.lang.Thread.run(Thread.java:748)
ConcurrentModificationException 就是常见的并发修改异常。
三、原因分析
多个线程同时读和写,会造成数据不一致,进而报并发修改异常。
补充:ArrayList 线程不安全还有其它情形,可以参考ArrayList 为什么线程不安全
四、解决方案
正因为 ArrayList 在多线程环境下是不安全的,那么我们只需要换一个线程安全的不就好了嘛。
所以,我们就能通过如下三种方案来进行解决。
(一)Vector
ArrayList 改成 Vector 集合类:
List<String> list = new Vector<>();
(二)synchronizedList
通过 Collections 工具类将 ArrayList 转换为线程安全的 ArrayList:
List<String> list = Collections.synchronizedList(new ArrayList<>());
(三)CopyOnWriteArrayList
使用并发编程类 CopyOnWriteArrayList 替换ArrayList:
List<String> list = new CopyOnWriteArrayList();
原理:写时复制
CopyOnWrite 容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器 Object[] 添加,而是现将当前容器 Object[] 进行Copy,复制出一个新的容器 Object[] newElements,然后新的容器 Object[] newElements 里添加元素,添加完元素之后,再将原容器的引用指向新的容setArray(newElements)。这样做的好处是可以对 CopyOnWrite 容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以 CopyOnWrite 容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWriteArrayList 类的 add() 方法源码如下:
public boolean add(E e) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); } }
推荐使用 CopyOnWriteArrayList 来进行解决。
五、Set和Map
同样,HashSet 和 HashMap 也是线程不安全的,因此也能用上述方法进行解决。
(一)HashSet
private static void setNotSafe() {
Set<String> set = new HashSet<>();
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
//Set<String> set = new CopyOnWriteArraySet<>();
for(int i=1; i<=30; i++){
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
}, String.valueOf(i)).start();
}
}
(二)HashMap
private static void mapNotSafe() {
Map<String,String> map = new HashMap<>();
//Map<String,String> map = new Hashtable<>();
//Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
//Map<String,String> map = new ConcurrentHashMap<>();
for(int i=1; i<=300; i++){
new Thread(()->{
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
}, String.valueOf(i)).start();
}
}
Java新手,若有错误,欢迎指正!