jdk1.8并发集合源码分析系列-CopyOnWriteArrayList和CopyOnWriteArraySet

127 阅读3分钟

CopyOnWriteArrayList介绍

官网介绍CopyOnWriteArrayList是一个ArrayList的线程安全的版本的实现,但是它保证线程安全的原理是add和set这系列写操作都是通过复制一个数组的副本来实现的,但是这也带来了高昂的性能消耗。

接口继承图以及相关方法分析

接口继承图和ArrayList一模一样,之前我们已经分析过了,可以点击这里回顾。

数据结构以及构造方法分析

	//重入锁,保障线程安全
    final transient ReentrantLock lock = new ReentrantLock();

    /** 存放数据的数组,只能通过getArray和setArray来访问 */
    private transient volatile Object[] array;

    /**
     * 获取存放数据的数组
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * 设置存放数据的数组
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * 构造方法:创建一个空的数组
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 创建一个包含指定集合的CopyOnWriteArrayList
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        //如果是CopyOnWriteArrayList,直接获取对应的array
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
        //否则调用集合的方法进行转换
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652) jdk bug 6260652
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

    /**
     * 创建包含「给定数组的深复制版本」的CopyOnWriteArrayList
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

分析:这里有两个小要点,第一,为了保证array在多线程下的可见性,用了volatile进行修饰。 第二,为了防止array被未知路径篡改,array这个变量被设置成private,同时getArray和setArray方法被设置成final,不允许重写以及method scope被设置成default,这样就只能就很好的保护了array变量。

重要方法分析

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;
          //替换CopyOnWriteArrayList数据结构中原来的数组
          setArray(newElements);
          return true;
      } finally {
          lock.unlock();
      }
  }

set方法

public E set(int index, E element) {
      final ReentrantLock lock = this.lock;
      lock.lock();
      try {
          Object[] elements = getArray();
          E oldValue = get(elements, index);

          if (oldValue != element) {
              int len = elements.length;
              //深复制出一个新的数组副本出来
              Object[] newElements = Arrays.copyOf(elements, len);
              //把新元素放到数组里面
              newElements[index] = element;
               //替换CopyOnWriteArrayList数据结构中原来的数组
              setArray(newElements);
          } else {
              // Not quite a no-op; ensures volatile write semantics
              setArray(elements);
          }
          return oldValue;
      } finally {
          lock.unlock();
      }
  }

其实看到这里,基本也就明白了CopyOnWriteArrayList的精髓,使用volatile保障array在多线程访问下的可见性。使用重入锁保证array的写入安全,最后咱们再看几个读方法的实现就可以了。

 public int size() {
      return getArray().length;
 }
 
 public int indexOf(Object o) {
    Object[] elements = getArray();
    return indexOf(o, elements, 0, elements.length);
}

通过上面的实现,咱们可以看到,读方法都是先通过getArray()获取当前array的副本,然后进行详情的数组操作就可以了。

CopyOnWriteArraySet介绍

CopyOnWriteArraySet你就可以理解为一个线程安全的set,但是它是用CopyOnWriteArrayList实现的,所以有CopyOnWriteArrayList一样的问题。

数据结构以及构造方法分析

//数据结构和CopyOnWriteArrayList一致
private final CopyOnWriteArrayList<E> al;

/**
 * Creates an empty set.
 */
public CopyOnWriteArraySet() {
    al = new CopyOnWriteArrayList<E>();
}

重要方法分析

add方法

//使用addIfAbsent方法完美的实现了add的效果
public boolean add(E e) {
    return al.addIfAbsent(e);
}

读方法

//直接取CopyOnWriteArrayList获取对应的size就行了
public int size() {
    return al.size();
}