瞄一眼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,也就不需要这一段了。

 

posted @ 2018-11-11 19:12  傅晓芸  阅读(827)  评论(0编辑  收藏  举报