集合线程不安全,请编写案例给出解决方案
本文内容:List、Set、Map的线程不安全演示、线程安全方案、写时复制的底层原理
List集合(Vector,Collections,CopyOnWriteArrayList)
1、故障现象:
java.util.ConcurrentModificationException
2、导致原因:
线程并发争抢修改导致,参考我们的花名册签名情况(一个人正在写入,另外一个同学过来抢夺,导致数据不一致异常。并发修改异常)。
3、解决方法:
①new Vector<>()
②Collections.synchronizedList(new ArrayList<>());
③new CopyOnWriteArrayList()
4、优化建议:
在读多写少的时候推荐使用 CopeOnWriteArrayList 这个类
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
//集合类不安全问题--ArrayList
public class NoSafeArrayList {
public static void main(String[] args) {
//notSafe();
//vectorTest();
//colletionsTest();
copyOnWriteArrayListTest();
}
//故障现象:java.util.ConcurrentModificationException
public static void notSafe() {
List<String> list = new ArrayList<>();
addOf100Thread(list);//用100个线程向list里加东西
}
//解决方案1:使用Vector (Vector源码里的方法加了Syn)
public static void vectorTest() {
List<String> list = new Vector<>();
addOf100Thread(list);
}
//解决方案2:使用Collections辅助类
public static void colletionsTest() {
List<String> list = Collections.synchronizedList(new ArrayList<>());//相当于给ArrayList一个"包书皮"
addOf100Thread(list);
}
//☆☆☆☆☆解决方案3:使用写时复制CopyOnWriteArrayList
public static void copyOnWriteArrayListTest() {
List<String> list = new CopyOnWriteArrayList<>();
addOf100Thread(list);
}
//工具方法:向list里使用100个线程添加字符
private static void addOf100Thread(List<String> list) {
for (int i = 1; i <= 100; i++)
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + "\t" + list);
}, "Thread " + i).start();
}
}
Set集合(Collections,CopyOnWriteArrayList)
1、故障现象:
java.util.ConcurrentModificationException
2、导致原因:
线程并发争抢修改导致,参考我们的花名册签名情况(一个人正在写入,另外一个同学过来抢夺,导致数据不一致异常。并发修改异常)。
3、解决方法:
①Collections.synchronizedSet(new HashSet<>());
②new CopyOnWriteArrayList()
4、优化建议:
在读多写少的时候推荐使用 CopeOnWriteXxx 这个类
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
public class NoSafeHashSet {
public static void main(String[] args) {
//notSafe();
//colletionsTest();
CopyOnWriteArraySet();
}
//故障现象:java.util.ConcurrentModificationException
public static void notSafe() {
Set<String> set = new HashSet<>();
addOf100Thread(set);//用100个线程向list里加东西
}
//解决方案1:使用Collections辅助类
public static void colletionsTest() {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
addOf100Thread(set);
}
//☆☆☆☆☆解决方案2:使用写时复制CopyOnWriteArrayList
public static void CopyOnWriteArraySet() {
Set<String> set = new CopyOnWriteArraySet<>();
addOf100Thread(set);
}
//工具方法:向set里使用100个线程添加字符
private static void addOf100Thread(Set<String> set) {
for (int i = 1; i <= 100; i++)
new Thread(() -> {
set.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + "\t" + set);
}, "Thread " + i).start();
}
}
附:HashSet源码
HashSet底层是HashMap,创建一个容器16,负载因子0.75的HashMap
但是HashMap是k v,那么HashSet底层用HashMap时,k和v是双列的,是因为v是常量
Map集合(Collections,ConcurrentHashMap)
1、故障现象:
java.util.ConcurrentModificationException
2、导致原因:
线程并发争抢修改导致,参考我们的花名册签名情况(一个人正在写入,另外一个同学过来抢夺,导致数据不一致异常。并发修改异常)。
3、解决方法:
①Collections.synchronizedMap(new HashMap<>());
②new ConcurrentHashMap()
4、优化建议:
在读多写少的时候推荐使用 CopeOnWriteXxx 这个类
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class NoSafeHashMap {
public static void main(String[] args) {
//notSafe();
//colletionsTest();
concurrentHashMap();
}
//故障现象:java.util.ConcurrentModificationException
public static void notSafe() {
Map<String,String> map = new HashMap<>();
addOf100Thread(map);//用100个线程向list里加东西
}
//解决方案1:使用Collections辅助类
public static void colletionsTest() {
Map<String,String> map = Collections.synchronizedMap(new HashMap<>());
addOf100Thread(map);
}
//☆☆☆☆☆解决方案2:使用写时复制CopyOnWriteArrayList
public static void concurrentHashMap() {
Map<String,String> map = new ConcurrentHashMap<>();
addOf100Thread(map);
}
//工具方法:向set里使用100个线程添加字符
private static void addOf100Thread(Map<String,String> map) {
for (int i = 1; i <= 100; i++)
new Thread(() -> {
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + "\t" + map);
}, "Thread " + i).start();
}
}
ConcurrentHashMap的解读在另一个文档解读里(C:\Users\JIAN\Desktop\个人整理)
CopyOnWriteXxx写时复制容器(对List、Set)
CopyOnWrite容器即写时复制的容器。(CopyOnWriteHashSet底层是CopyOnWriteArrayList)
往一个容器添加元素的时候,不直接往当前容Object[]添加,而是:------
先将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后,
再将原容器的引用指向新的容器setArray(newElements);。
这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。
所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器(读的是旧容器,写的是copy版+1)。
public boolean add(E e)
{
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();
}
}