文章目录
- 1、ArrayList集合线程安全问题分析
- [2、解决方式一:Vector或synchronizedList( )](#2、解决方式一:Vector或synchronizedList( ))
- [3、解决方式二:CopyOnWriteArrayList 写时复制](#3、解决方式二:CopyOnWriteArrayList 写时复制)
- 4、HashSet集合线程不安全的分析与解决
- 5、HashMap集合线程不安全的分析与解决
1、ArrayList集合线程安全问题分析
对List集合非线程安全的Demo代码:
java
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
//多个线程同时写入List集合
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//加元素
list.add(UUID.randomUUID().toString().substring(0,8));
//遍历输出集合
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
运行:

ConcurrentModificationException异常,是在多线程环境下,当一个线程正在遍历集合,而另一个线程对集合进行了修改操作时,就会抛出这个异常。以ArrayList为例,其add方法源码,未加synchronized关键字:

再点击报错详情,进入抛出异常的方法:

modCount即集合新增的次数,是实际修改次数,而expectedModCount是预期修改次数,它是ArrayList的一个内部类Itr的成员变量,调用iterator()获取迭代器时,内部创建Itr对象,此时,modCount会赋值给expectedModCount:

拿到迭代器对象,要遍历集合时,modCount已经赋值给expectedModCount,而此时其他线程继续add,modCount+1,modCount和expectedModCount就不相等了。
2、解决方式一:Vector或synchronizedList( )
List接口的另一个实现类Vector,其add方法加了关键字,使用它可解决线程安全问题,但很古老了,since1.2,很少用了。

java
List<String> list = new Vector<>();
//重复代码略
同样一种古老的解决方案,可以用Collections的synchronizedList方法,传入一个有线程安全问题的List,如ArrayList:
java
List<String> list = Collections.synchronizedList(new ArrayList<>());
3、解决方式二:CopyOnWriteArrayList 写时复制
java
List<String> list = new CopyOnWriteArrayList<>();
完整demo:
java
public class ArrayListDemo {
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
//多个线程同时写入List集合
for (int i = 0; i < 10; i++) {
new Thread(() -> {
//加元素
list.add(UUID.randomUUID().toString().substring(0,8));
//遍历输出集合
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
原理是写时复制技术,即:
- 对这个List实现类的集合,可以多线程并发读
- 往集合中写的时候,则只能独立写,先复制一份原来的集合,这个时候读还是读原来的,然后往新集合里面写入新的内容
- 写完后新旧合并,再读时,就读这个合并后的集合

看下源码,再对照着理解写时复制:

4、HashSet集合线程不安全的分析与解决
java
public class HashSetDemo {
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
//写入
set.add(UUID.randomUUID().toString().substring(0,8));
//读
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
运行:

解决办法类比上面的List,使用CopyOnWriteArraySet
:
java
Set<String> set = new CopyOnWriteArraySet<>();
5、HashMap集合线程不安全的分析与解决
java
public class HashSetDemo {
public static void main(String[] args) {
Map<String,string> map = new HashMap<>();
for (int i = 0; i < 30; i++) {
String key = String.valueOf(i);
new Thread(() -> {
//写入
map.put(key,UUID.randomUUID().toString().substring(0,8));
//读
System.out.println(map);
},String.valueOf(i)).start();
}
}
}

解决办法类比List,用ConcurrentHashMap
:
java
Map<String,String> map = new ConcurrentHashMap<>();