List的线程安全
先来看这么一段代码,我们创建了一个ArrayList,在循环中通过两个线程对这个list进行并发修改
package com.lchtest.juc;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;
public class CopyOnWriteArraySetTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 1; i <= 10; i++){
int temp = i;
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,6));
System.out.println(list);
},String.valueOf(temp)).start();
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,6));
System.out.println(list);
},String.valueOf(temp)).start();
}
}
}
执行结果如下,报了一个ConcurrentModificationException:
为什么会产生并发修改异常?因为arraylist的源码中,add方法没有加同步锁
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
那么怎么才能正确的对arraylist并发修改呢?只需要把ArrayList换成CopyOnWriteArrayList即可: List list = new CopyOnWriteArrayList<>(); 那么它是如何实现的,来看源码
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray(); // 获取list中原来的元素
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1); // 新创建一个数组,保存原有的和新增的元素
newElements[len] = e; // 新数组中添加新元素
setArray(newElements); // 将数组引用指向新数组
return true;
} finally {
lock.unlock();
}
}
CopyOnWriteArrayList是线程安全的 ArrayList,它的add操作进行了加锁,同一时刻只能有一个线程对list进行添加操作,和 ArrayList 一样,它是个可变数组,不同点在于,它采用了写时复制的思想,当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据,那么CopyOnWriteArrayList是怎么解决这个问题的? 来看它的setArray方法:
private transient volatile Object[] array;
final void setArray(Object[] a) {
array = a;
}
这个array是加了volatile关键字的,volatile保证了线程对共享变量操作的结果对其他线程可见,一个线程读取 volatile 数组时,总能看
到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证。
CopyOnWriteArrayList 缺点:
由于CopyOnWriteArrayList 在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。
解决ArrayList并发修改问题:使用CopyOnWriteArrayList
解决HashSet并发修改问题: 使用CopyOnWriteArraySet
解决hashmap线程不安全问题:ConcurrentHashMap