目录
写在前面
很多小伙伴使用ArrayList、HashMap等集合类,大多数情况下都不是用在多线程环境下的。
那么,多线程环境下是否仍然可以使用ArrayList、HashMap等集合类呢?
答案:不可以哦。
一、ArrayList并发不安全
1.验证ArrayList并发不安全,以及解决方案
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class ArrayListNotSafeDemo {
public static void main(String[] args) {
// 1.ArrayList线程不安全的示例
//List<String> list = new ArrayList<>();
// 2.Vector 是线程安全的,但是太古老了
//List<String> list = new Vector<>();
// 3.使用Collections类生成一个线程安全的ArrayList
//List<String> list = Collections.synchronizedList(new ArrayList<String>());
// 4.CopyOnWriteArrayList 写时复制,读写分离的思想,线程安全
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() ->{
// java.util.ConcurrentModificationException
list.add(UUID.randomUUID().toString().substring(0, 8));
// 多线程下会造成数据问题。
System.out.println(list);
}).start();
}
/**
* 1.故障现象
* java.util.ConcurrentModificationException
*
* 2.导致原因
* ArrayList线程不安全
*
* 3.解决方案
* Vector
* Collections.synchronizedList
* CopyOnWriteArrayList
*/
}
}
2.CopyOnWriteArrayList解析 写时复制 读写分离
/*
CopyOnWriteArrayList的add方法 添加时,1.先复制,2.再添加,3.再换引用
写时复制:
CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
*/
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();
}
}
二、集合类Set并发不安全
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
public class SetNotSafeDemo {
public static void main(String[] args) {
// 1.并发不安全
//Set<String> set = new HashSet<>();
// 2.Collections.synchronizedSet
//Set<String> set = Collections.synchronizedSet(new HashSet<>());
// 3. CopyOnWriteArraySet 底层其实还是一个CopyOnWriteArrayList
Set<String> set = new CopyOnWriteArraySet();
for (int i = 0; i < 30; i++) {
new Thread(() ->{
// java.util.ConcurrentModificationException
set.add(UUID.randomUUID().toString().substring(0, 8));
// 多线程下会造成数据问题。
System.out.println(set);
}).start();
}
/**
* 1. HashSet底层就是一个HashMap。
* 2. HashSet的add方法只放一个参数,key就是这个参数,value是一个Object,是恒定的。
*/
}
}
三、Map线程不安全
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
public class MapNotSafeDemo {
public static void main(String[] args) {
// 1.线程不安全
//Map<String, String> map = new HashMap<>();
// 2.Collections.synchronizedMap
//Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
// 3.ConcurrentHashMap是线程安全的,分段锁,16段
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 30; i++) {
new Thread(() ->{
// java.util.ConcurrentModificationException
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
// 多线程下会造成数据问题。
System.out.println(map);
}).start();
}
}
}