ArrayList是线程不安全的集合,他的所有方法都没有加synchronized锁进行修饰,所以容易出现线程不安全的问题我们看一下下面的例子。
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List list = new ArrayList<>();
for (int i = 1; i <=3; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
一共有3个线程同时修改list这一公共资源,最后结果如果是线程安全的情况下应该是第一次list里面有一个值,第二个有两个值,第三个有三个值。来看一下结果。
结果是在线程1添加完之后线程2添加完时按理说应该打出,但是这时候线程3进来了,拿到主物理内存的两个元素又填加了一个值然后输出结果是3个。然后线程2才把自己的工作空间的有两个值的list覆盖掉主物理内存然后输出。这时候就得到了不正确的结果。
然后我们将线程数改成60个。这时候就会报java.util.ConcurrentModificationException这个错误,这个问题是因为ArrayList是线程不安全的,所以我们可以将ArrayList换成Vector,Vector中的add方法是加了synchronized。同时我们还可以用Collections.synchronizedList(new ArrayList<>());第三种方法就是我们最终引出来的CopyOnWriteArrayList<>()写时复制。
2. 通过CopyOnWriteArrayList解决集合不安全问题如果我们将上面的List改成CopyOnWriteArrayList就可以解决上述问题。这个类按字面意思就是写时复制,如下图所示。
写时复制就是一个集合里的内容可以并发读但是一旦要修改容易造成线程不安全问题,所以我们可以在写的时候复制一份写入新内容,然后覆盖掉之前的旧的集合,这样的话大家再读就是新内容了。让我们来看一下这个类的add方法是如何保证这种写时复制的。
public boolean add(E e) {
synchronized (lock) {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
他会复制之前的数组然后再去写,然后再通过setArray这个方法覆盖掉之前的内容完成写时复制的效果。



