线程不安全综述
线程不安全案例
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();
}
}
运行时会报错
java.util.ConcurrentModificationException
原因分析
举个例子,就好比上课前签到,每个人都有一支笔,但是只有一张签到纸。张三在纸上签到,‘张’字刚写完,李四就过来强硬的把纸拉走,要签到,但是在拉的过程中,张三的笔就会在纸上留下长长的划痕。然后李四签到。有可能李四签到完成正常轮到下一位,也有可能李四还没签完就又被抢走了。
上面例子中,拿着笔的同学就好比是线程,在抢夺‘纸’的过程中在‘纸’上留下的划痕就好比是程序抛的异常。
解决方案
1. List list = new Vector<>();
Vector底层都有synchronized封装,可以保证线程安全。
2. List list = Collections.synchronizedList(new ArrayList<>());
用Collections包装类的synchronizedList方法将ArrayList包装起来,ArrayList也可以变成线程安全。
3.List list = new CopyOnWriteArrayList<>();
写时复制
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();
}
}
举个例子,还是上面的签到,比如前面已经有一些同学签到了,轮到张三签到了,张三会把签到表抄一份一模一样的到自己手里(上面的数组复制,并且长度+1,因为指望里面添加一个元素(姓名)),然后在最后面加上自己的名字,然后把手里的签到表公布出来,说:“现在的签到表以这份为准,以前的签到表已经作废。”(这里setArray将新的集合作为标准,以前的指针指向打断了)。