package thread;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
public class ContainerNotSafeDemo {
public static void main(String[] args) {
listNotSafe();
setNoSafe();
mapNotSafe();
}
private static void mapNotSafe() {
//Map<String,String> map=new HashMap<>();
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + "\t" + map);
}, String.valueOf(i)).start();
}
}
private static void setNoSafe() {
//Set<String> set=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(Thread.currentThread().getName() + "\t" + set);
}, String.valueOf(i)).start();
}
}
private static void listNotSafe() {
//List<String> list=new ArrayList<>();//多线程下ArrayList线程不安全,会发生并发修改的异常java.util.ConcurrentModificationExecption
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(Thread.currentThread().getName() + "\t" + list);
}, String.valueOf(i)).start();
}
}
}
一、ArrayList 线程不安全:
复习:
当 new ArrayList<Integer>() 后,底层new了什么:一个数组;什么类型的数组:Object[],默认为空 初试容量为10 的动态数组。每次扩容扩一半容量。add时长度每次增加1
ArrayList 在add操作时 为了性能,没有加锁、没有保证线程安全的措施,多线程下 会发生并发修改的异常 java.util.ConcurrentModificationException
异常现象
多线程下ArrayList会发生并发修改异常,产生java.util.ConcurrentModificationExecption
导致原因
并发争抢修改导致
一个线程正在写入,另外一个线程抢夺执行权,导致数据不一致,并发修改异常
解决方案
不能仅仅说加锁来解决!
(因为更早版本java 1.0 出来的Vector 是加了synchronized锁的,而后出来的ArrayList未加锁,Vector不会报ConcurrentModificationExecption。仅仅加锁为什么还用ArrayList呢,直接用Vector不就好了。加锁,并发性能急剧下降 )
解决方案:
-
使用Vector,只是不推荐,并发性能下降
- 使用Collections.synchronizeList(new ArrayList<>())
-
使用CopyOnWriteArrayList() 写时复制
CopyOnWriteArrayList可以保证线程安全又能兼顾并发性能的原因:
写时复制
CopyOnwriteArrayList 容器即写时复制容器。往一个容器add元素时,不直接往容器Object[]添加,而是现将当前容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后往新的容器Object[] new Elements 里添加元素,添加完元素之后,再将原容器的引用指向新的容器 setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
public boolean add(E paramE)
{
ReentrantLock localReentrantLock = this.lock;
localReentrantLock.lock();
try
{
Object[] arrayOfObject1 = getArray();
int i = arrayOfObject1.length;
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);
int j = 1;
return j;
}
finally
{
localReentrantLock.unlock();
}
}