CopyOnWriteArrayList core source code analysis

Picture title

I kept running just to catch up with myself who had high hopes. -Livingston

0 Preface

We know that ArrayList non-thread-safe, you need to own or use a lock Collections.synchronizedListpackaging container provides concurrency thread-safe mechanisms to achieve from using CopyOnWrite JDK1.5 start JUC in the List -. CopyOnWriteArrayList, referred COW

Picture title

1 CopyOnWrite design ideas

1.1 Basic concepts

CopyOnWrite copy-on-write. Generally speaking, when we add elements to a container, we don't directly add to the current container, but first copy the current container out of a new container, add elements to the new container, add elements Then, point the original container to the new container. That is, everyone is sharing the same content at the beginning. When someone wants to modify the content, they will actually copy the content to form a new content and then change it. This is a kind of Delay lazy strategy.

1.2 Design advantages

The CopyOnWrite container can be read concurrently without locking, because the current container will not add any elements. So this is also a separate read and write idea, read and write different containers.

2 Inheritance system

  • Similar to the inheritance system of ArrayList
    Picture title
    Picture title

3 Properties

  • Locks to protect all modifiers
    Picture title
  • Arrays that can only be accessed via getArray / setArray
    Picture title
  • lock memory offset
    Picture title

4 Construction method

4.1 No parameters

  • Create an empty list
    Picture title

4.2 Participation

  • Create a list that contains the elements of the specified collection, whose order is returned by the collection's iterator.
    Picture title
  • Create a list holding a copy of the given array
    Picture title

Let's start looking at the source code and how to copy it on write.

5 add (E and)

Adding elements to the COW requires locking, otherwise N copies will be copied during concurrent writing!

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 1.加锁
    lock.lock();
    try {
        // 得到原数组
        Object[] elements = getArray();
        int len = elements.length;
        // 2.复制出新数组,加一是因为要添加yi'ge'yuan's
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 把新元素添加到新数组里,直接放在数组尾部
        newElements[len] = e;
        // 把原数组引用指向新数组
        setArray(newElements);
        return true;
    } finally {
        // finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁 
        lock.unlock();
    }
}
复制代码

getArray

  • Get an array. Non-priavte, so that it can also be accessed from the CopyOnWriteArraySet class (which directly combines CopyOnWriteArrayList as a member variable)
    Picture title

setArray

  • Set reference to new array
    Picture title

Both are locked, why do you need to copy the array without directly modifying the original array?

  • volatile is modified by an array reference! Simply modify the value of a few elements in the original array. This operation cannot be used for visibility. You must modify the memory address of the array
  • Executing copyOf on the new array has no effect on the original array. Only after the new array is completely copied, can it be accessed externally, avoiding the possible adverse effects of data changes in the original array

6 get

get(int index)

  • Read the specified position element
    Picture title

get(Object[] a, int index)

Picture title

There is no need to add a lock when reading. If other threads are adding data to the ArrayList while reading, the read will still only read the old data, because the old array will not be locked when writing.

7 remove

7.1 Delete specified index

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 {
            // 若删除的数据在数组中间:
            // 1. 设置新数组的长度减一,因为是减少一个元素
            // 2. 从 0 拷贝到数组新位置
            // 3. 从新位置拷贝到数组尾部
            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();
    }
}
复制代码

Still three axes:

  1. Lock
  2. Depending on where the index is deleted, make different policy copies
  3. Unlock

7.2 Bulk delete

public boolean removeAll(Collection<?> c) {
    if (c == null) throw new NullPointerException();
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        if (len != 0) {
            // newlen 表新数组的索引位置,新数组中存在不包含在 c 中的元素
            int newlen = 0;
            Object[] temp = new Object[len];
            // 循环,把不包含在 c 里面的元素,放到新数组中
            for (int i = 0; i < len; ++i) {
                Object element = elements[i];
                // 不包含在 c 中的元素,从 0 开始放到新数组中
                if (!c.contains(element))
                    temp[newlen++] = element;
            }
            // 拷贝新数组,变相的删除了不包含在 c 中的元素
            if (newlen != len) {
                setArray(Arrays.copyOf(temp, newlen));
                return true;
            }
        }
        return false;
    } finally {
        lock.unlock();
    }
}
复制代码

Instead of directly deleting the array elements one by one, first judge the array values ​​in a loop, put the data that does not need to be deleted into the temporary array, and finally the data in the temporary array is the data that we don't need to delete.

8 Summary

The CopyOnWrite concurrency container is suitable for concurrent scenarios with more reads and less writes. The CopyOnWrite container has many advantages, but there are also problems. You need to pay attention to when developing:

Memory usage issues

When writing, the memory of two objects will reside in the memory at the same time, the old object and the newly written object (only the reference in the container is copied when copying, but the new object is created and added to the new container when writing, and the old The objects of the container are still in use, so there are two copies of the object memory). If these objects occupy a large amount of memory, it is likely to cause frequent GC, and the application response time becomes longer. Memory, or not directly use the CopyOnWrite container, but use other concurrent containers, such as ConcurrentHashMap.

Data consistency issues

The CopyOnWrite container can only guarantee data, 最终一致性but not data 实时一致性, please use it as appropriate.

Guess you like

Origin juejin.im/post/5e93281d51882573716a9c8b