首页 > 技术文章 > 并发容器-CopyOnWriteArrayList

ningbing 2021-03-17 14:11 原文

并发容器一览
  源:https://time.geekbang.org/column/article/90201?utm_term=pc_interstitial_938
CopyOnWriteArrayList
       该容器支持多个线程同时读(读不加锁),但同一时刻只能一个线程写(写加锁)。
  内部维护了一个数组
1 /** The array, accessed only via getArray/setArray. */
2 private transient volatile Object[] array;
1.获取元素
可以看到没有做任何处理,直接返回数组中的元素。读取元素时是不加锁的,也就是可以任意线程同时读取。
 1 /**
 2  * {@inheritDoc}
 3  *
 4  * @throws IndexOutOfBoundsException {@inheritDoc}
 5  */
 6 public E get(int index) {
 7     return get(getArray(), index);
 8 }
 9 /**
10  * Gets the array.  Non-private so as to also be accessible
11  * from CopyOnWriteArraySet class.
12  */
13 final Object[] getArray() {
14     return array;
15 }
16 @SuppressWarnings("unchecked")
17 private E get(Object[] a, int index) {
18     return (E) a[index];
19 }
2.更新元素
  可以看到,是直接深拷贝了原数组,然后修改新数组的值,最后将对象的数组指向新数组。更新元素是加了可重入锁的,同一时刻只有一个线程能够修改数组的值,其他线程要修改只能等待获得锁。
 1 /**
 2  * Replaces the element at the specified position in this list with the
 3  * specified element.
 4  *
 5  * @throws IndexOutOfBoundsException {@inheritDoc}
 6  */
 7 public E set(int index, E element) {
 8     final ReentrantLock lock = this.lock;
 9     lock.lock();
10     try {
11         Object[] elements = getArray();
12         E oldValue = get(elements, index);
13         // 判断原有位置元素是否和插入元素相同,不相同才插入
14         if (oldValue != element) {
15             int len = elements.length;
16             Object[] newElements = Arrays.copyOf(elements, len);
17             newElements[index] = element;
18             setArray(newElements);
19         } else {
20             // Not quite a no-op; ensures volatile write semantics
21             // 不是无用操作,是为了保证volatile写的意义
22             setArray(elements);
23         }
24         return oldValue;
25     } finally {
26         lock.unlock();
27     }
28 }
29 /**
30  * Sets the array.
31  */
32 final void setArray(Object[] a) {
33     array = a;
34 }
22行代码处的setArray(elements),这里操作看似没有意义。因为elements是volatile修饰的, 这里是为了保证happens-before原则:
  1.volatile变量读操作happens-before写操作;
  2.同一线程的代码顺序执行,先前的操作 happens-before 之后的操作
  3.传递性
实际上这是为了保证非volatile变量的可见性。
像下面这个例子,线程1的set操作可以保证nonVolatileField 变量的修改对线程2的读取可见。
// initial conditions
int nonVolatileField = 0;
CopyOnWriteArrayList<String> list = /* a single String */

// Thread 1
nonVolatileField = 1;                 // (1)
list.set(0, "x");                     // (2)

// Thread 2
String s = list.get(0);               // (3)
if (s == "x") {
    int localVar = nonVolatileField;  // (4)
}
  好像JDK11移除了这行代码。。。
 
3.插入元素
 1 /**
 2  * Appends the specified element to the end of this list.
 3  * 末尾增加元素
 4  * @param e element to be appended to this list
 5  * @return {@code true} (as specified by {@link Collection#add})
 6  */
 7 public boolean add(E e) {
 8     final ReentrantLock lock = this.lock;
 9     lock.lock();
10     try {
11         Object[] elements = getArray();
12         int len = elements.length;
13         Object[] newElements = Arrays.copyOf(elements, len + 1);
14         newElements[len] = e;
15         setArray(newElements);
16         return true;
17     } finally {
18         lock.unlock();
19     }
20 }
21 /**
22  * Inserts the specified element at the specified position in this
23  * list. Shifts the element currently at that position (if any) and
24  * any subsequent elements to the right (adds one to their indices).
25  *
26  * @throws IndexOutOfBoundsException {@inheritDoc}
27  */
28 public void add(int index, E element) {
29     final ReentrantLock lock = this.lock;
30     lock.lock();
31     try {
32         Object[] elements = getArray();
33         int len = elements.length;
34         if (index > len || index < 0)
35             throw new IndexOutOfBoundsException("Index: "+index+
36                                                 ", Size: "+len);
37         Object[] newElements;
38         int numMoved = len - index;
39         if (numMoved == 0)
40             newElements = Arrays.copyOf(elements, len + 1);
41         else {
42             newElements = new Object[len + 1];
43             System.arraycopy(elements, 0, newElements, 0, index);
44             System.arraycopy(elements, index, newElements, index + 1,
45                              numMoved);
46         }
47         newElements[index] = element;
48         setArray(newElements);
49     } finally {
50         lock.unlock();
51     }
52 }

  可以看到,增加元素和更新元素一样,也是创建一个新数组复制原有元素,然后插入新增元素。同时也是加了可重入锁,同一时刻只有一个线程能进行修改。

4.删除元素
/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).  Returns the element that was removed from the list.
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            Object[] newElements = new Object[len - 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
  可以看到也是创建了一个新数组,直接复制原数组元素。然后将数组引用指向新数组。

推荐阅读