CopyOnWriteArrayList是一个并发集合类,它实现了List接口,它是一个线程安全类而且它实现线程安全的机制和Vector不一样。Java API文档说它是通过复制一个内部数组来实现写操作(包括add、set等等)的线程安全机制。这种方式会很耗费性能,但是在遍历操作次数远大于写操作的时候效率会很高。这种复制模式称为“snapshot style”,当调用iterator方法创建迭代器时,迭代器保留一个指向底层数组的引用,在迭代器的生命周期内,这个数组永远不会改变,并且迭代器不支持写操作,会产生UnsupportedOperationException异常。
来看看代码:
1 /** The lock protecting all mutators */ 2 transient final ReentrantLock lock = new ReentrantLock(); 3 4 /** The array, accessed only via getArray/setArray. */ 5 private volatile transient Object[] array;
从代码中可以看出,CopyOnWriteArrayList用关键字volatile修饰了数组array,这样可以确保每次遍历list时取得的数组内容都是最新的。另外还定义了一个ReentrantLock类型的lock,用作写操作时的加锁用。
来看看写操作的代码:
/** * Replaces the element at the specified position in this list with the * specified element. * * @throws IndexOutOfBoundsException {@inheritDoc} */ public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); Object oldValue = elements[index]; if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return (E)oldValue; } finally { lock.unlock(); } }
在这个set方法,首先会取得lock,lock.lock(),然后定义oldValue赋值为替换前的值,接着赋值一个数组newElements,将这个新数组的对应下标的值替换为新的值。最后调用setArray方法,把底层数组替换为新数组。最后再释放lock,lock.unlock()。如果在这中间我们调用了iterator方法获取了迭代器,那么迭代器用到的数组就不是新数组,还是老的数组,迭代器如果要取得最新的数组,则必须再调用iterator方法。
看个例子:
1 public static void main(String[] args) { 2 CopyOnWriteArrayList list = new CopyOnWriteArrayList(); 3 list.add(1); 4 list.add(2); 5 list.add(3); 6 Iterator it = list.iterator(); 7 list.add(4); 8 while (it.hasNext()) { 9 int i = (Integer) it.next(); 10 System.out.println(i); 11 } 12 System.out.println(list); 13 }
这个方法的输出是:
1 1 2 2 3 3 4 [1, 2, 3, 4]
足以证明它们操作的不是同一个数组。