CopyOnWriteArrayList and CopyOnWriteArraySet of concurrent containers

CopyOnWriteArrayList

1. Class structure

[External chain image transfer failed, the source site may have an anti-theft chain mechanism, it is recommended to save the image and upload it directly (img-Gl6NVgDf-1585574481170) (C: \ Users \ MI \ AppData \ Roaming \ Typora \ typora-user-images \ 1585219561302.png)]

You can see that it implements the following interfaces from left to right:

  1. Cloneable: Cloneable
  2. List: implements all operations in List
  3. RnadomAccess: random access
  4. Setializable: serializable

Second, the class attributes

 /** 这里使用可重入锁 */
    final transient ReentrantLock lock = new ReentrantLock();

    /**存储数据的数组 */
    private transient volatile Object[] array;

Third, the constructor

3.1 Default construction

 public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
final void setArray(Object[] a) {
        array = a;
}

The default construction initializes an empty array of length 0 by default

3.2 Other structures

public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
    	//如果c是CopyOnWriteArrayList的,则直接转为数组赋值
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            //否则转为数组
            elements = c.toArray();
           //如果该数组不是Object类型的,则拷贝为Object类型数组后赋值
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }

** CopyOnWriteArrayList (E [] toCopyIn) ** Construction

Copy toCopyIn as an array of Object type

    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

Four, methods

4.1 add(E x)

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
    	//上锁
        lock.lock();
        try {
            //获取原数组
            Object[] elements = getArray();
            int len = elements.length;
            //拷贝原数组,长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //加入新的元素
            newElements[len] = e;
            //新数组替换掉旧数组
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
  1. Lock first
  2. Get the original array to copy, length +1
  3. Add new element to the end
  4. Replace the old array
  5. Release lock

4.2 add(int index, E element)

public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
     	//1. 上锁
        lock.lock();
        try {
            //获取原数组
            Object[] elements = getArray();
            int len = elements.length;
            //检索index
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            //新数组
            Object[] newElements;
            //要移动的位置的起点,将index往后的元素都要移动
            int numMoved = len - index;
            if (numMoved == 0)//如果要在第len个位置添加,则数组长度不够,拷贝原数组,长度+1
                newElements = Arrays.copyOf(elements, len + 1);
            else {//要在数组中间插入
                //新建一个长度为len+1的数组
                newElements = new Object[len + 1];
                //将index前的元素拷贝到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                //将index及其以后的元素拷贝到新数组index+1开始的位置,这样留出第index位置插入新元素
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //插入新元素
            newElements[index] = element;
            setArray(newElements);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

Process:

  1. Lock first, then check subscript index
  2. If insert at the end, copy the array and add 1 to the length
  3. If inserting in the middle, copy the new array, copy the elements of the old array with the index as the boundary, and copy to the [0, index-1] and [index + 1, ...] positions of the new array, leaving the index position
  4. Overwrite the old array after placing the new element at the index position of the new array
  5. Release lock

The addAll method is similar to the above two logics, and will not be described in detail

4.3 get()

 public E get(int index) {
        return get(getArray(), index);
    }
 private E get(Object[] a, int index) {
        return (E) a[index];
    }

Directly return the element at the index position of the array

4.4 remove()

public E remove(int index) {
        final ReentrantLock lock = this.lock;
    //上锁
        lock.lock();
        try {
            //旧数组
            Object[] elements = getArray();
            int len = elements.length;
            //index位置元素
            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];
                //将index以前的元素拷贝到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                //将index以后的数组拷贝到新数组,这样index位置的元素就移除了
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                //赋值数组
                setArray(newElements);
            }
            //返回删除的元素
            return oldValue;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
  1. Lock
  2. Verify index
  3. If the last element is deleted, the copy before the last element is copied to the new array and the old array is overwritten
  4. If you delete the middle element, create a new array, copy the elements before and after index to the new array, so that the element at index is removed, and finally the new array overwrites the old array
  5. Returns the value of the deleted element
  6. Release lock

4.4 removeAll()

 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) {//原数组有元素
               
                int newlen = 0;
                //构造长度为len的临时数组
                Object[] temp = new Object[len];
                for (int i = 0; i < len; ++i) {
                    //遍历旧数组,将不在被删除集合的元素加入到临时数组
                    Object element = elements[i];
                    if (!c.contains(element))
                        temp[newlen++] = element;
                }
                if (newlen != len) {
                    //覆盖旧数组
                    setArray(Arrays.copyOf(temp, newlen));
                    return true;
                }
            }
            //原数组为空,返回false
            return false;
        } finally {
            lock.unlock();
        }
    }
  1. Lock
  2. If the original array has a value, construct a new array, traverse the old array, put the elements that do not need to be deleted into the new array, and finally overwrite the old array, return true
  3. If the original array is empty, it returns false
  4. Release lock

4.5 addIfAbsent (E e)

If the element is not in the collection, add the element

 public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();//获取原数组
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false ://如果原数组不存在该元素,则返回false
            addIfAbsent(e, snapshot);
    }
private boolean addIfAbsent(E e, Object[] snapshot) {
        final ReentrantLock lock = this.lock;
        lock.lock();//上锁
        try {
            //再次获取旧数组
            Object[] current = getArray();
            int len = current.length;
            //如果和之前获得的旧数组不一样,说明数组被修改了
            if (snapshot != current) {
               
                int common = Math.min(snapshot.length, len);
                //再次检查元素是否在数组中
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))//i位置的元素已改变,且是当前元素,已存在,返回false
                        return false;
                
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            //构造一个长度为n+1的数组
            Object[] newElements = Arrays.copyOf(current, len + 1);
            //添加新元素
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

After the analysis, you will find that except for the read operation, the idea of ​​modification and deletion is basically the same.

V. Summary

  1. Internally use ReentrantLock to achieve thread safety
  2. The constructor initializes the array length to 0 by default
  3. Read operation without locking, directly return the corresponding position element
  4. Both write and read operations are done by first locking and then copying the old array. The length of the new array is the length after addition or deletion, (the length of the array is always the number of elements), and finally the old array is replaced Release lock
  5. The concurrent container is suitable for the scenario of reading more and writing less. Modification and deletion need to be copied, which is expensive
  6. Due to the idea of separate read and write , the old value may be read when reading, which can only guarantee the final consistency of the data
  7. Copy-on-write will cause two large arrays to exist in the memory if the object is too large, which will trigger youngGC or FullGC, resulting in STW and reduced performance

CopyOnWriteArraySet

First, the class attribute

private final CopyOnWriteArrayList<E> al;

It can be seen that the bottom layer is based on CopyOnWriteArrayList

Second, the constructor

//默认构造直接使用 CopyOnWriteArrayList的默认构造
public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

  	//
    public CopyOnWriteArraySet(Collection<? extends E> c) {
        if (c.getClass() == CopyOnWriteArraySet.class) {//如果是CopyOnWriteArraySet类型就直接将调用其参数为集合的构造
            @SuppressWarnings("unchecked") CopyOnWriteArraySet<E> cc =
                (CopyOnWriteArraySet<E>)c;
            al = new CopyOnWriteArrayList<E>(cc.al);
        }
        else {
            //如果不是,则调用addAllAbsent方法将c添加到集合中,如果已经存在(即有重复的)就不会添加
            al = new CopyOnWriteArrayList<E>();
            al.addAllAbsent(c);
        }
    }

Three, add

  public boolean add(E e) {
        return al.addIfAbsent(e);
    }

This is CopyOnWriteArrayListthe addIfAbsentmethod called internally . If the element already exists, it will not be added and the uniqueness is guaranteed.

The other methods of CopyOnWriteArraySet are CopyOnWriteArrayListthe methods used

Published 75 original articles · won praise 13 · views 8369

Guess you like

Origin blog.csdn.net/weixin_43696529/article/details/105209005