我们都知道ArrayList是线程不安全的,当我们在遍历ArrayList时,同时对其进行写操作会触发ConcurrentModificationException
import java.util.*;
public class Main {public static void main(String[] args) throws InterruptedException {List<String> a = new ArrayList<String>();a.add("a");a.add("b");a.add("c");final ArrayList<String> list = new ArrayList<String>(a);Thread t = new Thread(new Runnable() {int count = -1;@Overridepublic void run() {while (true) {list.add(count++ + "");}}});t.setDaemon(true);t.start();Thread.currentThread().sleep(3);for (String s : list) {System.out.println(s);}}
}
CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组。
当有新元素加入的时候,如下图,创建新数组,并往新数组中加入一个新元素,这个时候,array这个引用仍然是指向原数组的。
/*** Sets the array.*/final void setArray(Object[] a) {array = a;}
当元素在新数组添加成功后,将array这个引用指向新数组。
CopyOnWriteArrayList
的整个add操作都是在ReentrantLock锁的保护下进行的。
这样做是为了避免在多线程并发add的时候,复制出多个副本出来,把数据搞乱了,导致最终的数组数据不是我们期望的。
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();}}