在多线程中,若是A线程在读取一个List时,B线程在向里面add数据,会抛出异常:java.util.ConcurrentModificationExceptionjava
public class ErrorTest { public static void main(String[] args) throws InterruptedException { List<Integer> list = Arrays.asList(1, 2, 3, 4, 5); final ArrayList<Integer> list_thrd = new ArrayList<>(list); Thread thread = new Thread(new Runnable() { @Override public void run() { int count = 0; while (true) { list_thrd.add(count++); } } }); thread.setDaemon(true); thread.start(); Thread.sleep(3); for (Integer integer : list_thrd) { System.out.println(integer); } } }
运行这段代码,会抛出java.util.ConcurrentModificationException,即主线程在遍历list元素的时候,子线程在向里面添加元素。在这种状况下,咱们可使用CopyOnWriteArrayList进行操做。多线程
CopyOnWrite,字面意思就是写时复制。通俗的说,就是咱们向容器中添加数据的时候,并非向容器自己添加数据。而是,先copy一个副本,向里面添加记录,而后再将copy赋值给原对象。咱们来看下面的代码并发
public class CopyOnWriteListTest { public static void main(String[] args) throws InterruptedException { List<Integer> temp = Arrays.asList(1, 2, 3, 4, 5); final CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(temp); Thread thread = new Thread(new Runnable() { @Override public void run() { int count = 0; while (true) { list.add(count++); } } }); thread.setDaemon(true); thread.start(); Thread.sleep(3); for (Integer integer : list) { System.out.println(list.hashCode()); System.out.println(integer); } } }
咱们在遍历CopyOnWriteArrayList集合的时候,同时打印出hashCode,能够看到输出结果:ide
-1813306013 1 1381289098 2 1109838168 3 -1094901371 4 -1008565321 5 -1813169375 0 -1540165649 1 -1441641759 2 1807730922 3 -870434537 4 -1185576945 5
这说明CopyOnWriteArrayList的add方法确实是建立了新的list对象,同时也不会抛出异常。this
/** The lock protecting all mutators */ transient final ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private volatile transient Object[] array; 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(); } }
private E get(Object[] a, int index) { return (E) a[index]; }
get方法没有使用锁,因此读取的仍是旧的数据,由于写入的时候并无锁住旧的array对象spa
从刚刚的例子中咱们能够看出,CopyOnWriteArrayList适合读多写少的场景。例如黑白名单等,将数据初始化到List当中,当有较少写操做的时候,能够保证多并发状况下,List的最终一致性。可是,它也是有缺点的:当执行add操做的时候,因为要copy出一个对象,这时会致使内存占用加大。可能会形成频繁的Young GC和Full GC。线程