瞄一眼CopyOnWriteArrayList(jdk11)
CopyOnWriteArrayList是ArrayList线程安全的变体。使用写时复制策略进行修改操作。
与之前版本较明显的区别是,jdk11中用来保护所有设值方法(mutator)的ReentrantLock改为使用关键字synchronized。
文档中也明确表示相比较于ReentrantLock更倾向于使用内置锁(We have a mild preference for builtin monitors over ReentrantLock when either will do.)。
两个都是可重入独占锁,在不涉及到中断、超时等情况时,编码时使用synchronized明显比ReentrantLock优势得多。
CopyOnWriteArrayList的成员变量:
//锁对象 final transient Object lock = new Object(); //存储数据的数组 private transient volatile Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; }
第一个Object对象充当写时复制的锁对象,第二个volatile的array用来存放数据
挑个构造函数看看:
1 /** 2 * 根据Collection迭代器返回的顺序创建包含指定集合元素的列表 3 * 4 * @param c 最初保存元素的集合 5 * @throws NullPointerException 如果指定的集合为null 6 */ 7 public CopyOnWriteArrayList(Collection<? extends E> c) { 8 Object[] es; 9 if (c.getClass() == CopyOnWriteArrayList.class) 10 es = ((CopyOnWriteArrayList<?>)c).getArray(); //同类对象直接获取array赋值 11 else { 12 es = c.toArray(); 13 // defend against c.toArray (incorrectly) not returning Object[] 14 // (see e.g. https://bugs.openjdk.java.net/browse/JDK-6260652) 15 if (es.getClass() != Object[].class) 16 es = Arrays.copyOf(es, es.length, Object[].class); 17 } 18 setArray(es); //array赋值 19 }
注释中说,L15的条件分支是为了解决Collection.toArray()没有正确地返回为Object[]类型,而是错误地返回为Object类型。这个bug在jdk9已经被修复了。不知道为什么在11.0.2还留着。
jdk中的//单行注释一般都挺有意思的…
读操作和ArrayList没多大差别,所以都是弱一致性的。挑几个写操作看看:
1 public void add(int index, E element) { 2 synchronized (lock) { //获取独占锁 3 Object[] es = getArray(); 4 int len = es.length; 5 if (index > len || index < 0) { //越界校验 6 throw new IndexOutOfBoundsException(outOfBounds(index, len)); 7 } 8 Object[] newElements; 9 int numMoved = len - index; 10 if (numMoved == 0) { //若index=array.length,则新元素添加在末尾 11 newElements = Arrays.copyOf(es, len + 1); 12 } else { 13 newElements = new Object[len + 1]; //创建array副本,调用System.arraycopy()移动index后元素添加新元素于index 14 System.arraycopy(es, 0, newElements, 0, index); 15 System.arraycopy(es, index, newElements, index + 1, numMoved); 16 } 17 newElements[index] = element; 18 setArray(newElements); //将修改后的array副本回写给array 19 } 20 }
1 public E set(int index, E element) { 2 synchronized (lock) { //获取独占锁 3 Object[] es = getArray(); 4 E oldValue = elementAt(es, index); //找出index位置的元素 5 if (oldValue != element) { //如果元素与原先元素不同,则创建array副本。在副本修改后写回array 6 es = es.clone(); 7 es[index] = element; 8 setArray(es); 9 } 10 return oldValue; 11 } 12 }
值得注意的是,之前版本的set方法在上段代码L9多了个分支:
} else { // Not quite a no-op; ensures volatile write semantics setArray(elements); }
注释说了,setArray()是为了保持volatile写的语义,即内存一致性:当存在并发时,将对象放入CopyOnWriteArrayList之前的线程中的操作happen-before随后通过另一线程从CopyOnWriteArrayList中访问或移除该元素的操作。由于jdk11使用synchronized替代了ReentrantLock,也就不需要这一段了。