Multi-threaded high-concurrency programming (9) -- CopyOnWrite when writing

  CopyOnWrite copy on write

  CopyOnWrite, that is, snapshot mode, copy-on-write means that when different threads access the same resource, they will get the same pointer to this resource. Only in the write operation, will a copy of new data be copied, and then the new data will be written Immediately after the operation is completed, the latest data changes are seen by other threads, and the previously obtained pointers will point to the new data, but other threads can still access the original resources while the write operation is not over. The main advantage of this method is that if there is no thread to perform the write operation, the copy of the data copy will not be performed, so multiple threads can share the same resource when only reading.

  CopyOnWrite can read concurrently without locking. It is an idea of ​​separation of reading and writing, reading and writing different containers.

  Take CopyOnWriteArrayList as an example below:

  test:

   public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("a");
        list.add("b");
        list.add("c");
        System.out.println("主线程-0:"+list.toString());
        new Thread(()->{
            System.out.println("读子线程-0:"+list.toString());
        }).start();
        new Thread(()->{
            list.add("d");
            System.out.println("写子线程-0:"+list.toString());
        }).start();
        new Thread(()->{
            System.out.println("读子线程-1:"+list.toString());
        }).start();
        list.add("e");
        new Thread(()->{
            System.out.println("读子线程-2:"+list.toString());
        }).start();
        System.out.println("主线程-1:"+list.toString());
        new Thread(()->{
            list.add("f");
            System.out.println("写子线程-1:"+list.toString());
        }).start();
        System.out.println("主线程-2:"+list.toString());
        new Thread(()->{
            System.out.println("读子线程-3:"+list.toString());
        }).start();
    }
//=======结果========
主线程-0:[a, b, c]
读子线程-0:[a, b, c]
写子线程-0:[a, b, c, d]
读子线程-1:[a, b, c, d, e]//主线程写e立马被读子线程1发现
主线程-1:[a, b, c, d, e]//主线程写e后输出
读子线程-2:[a, b, c, d, e]
主线程-2:[a, b, c, d, e]
写子线程-1:[a, b, c, d, e, f]
读子线程-3:[a, b, c, d, e, f]

   CopyOnWriteArrayList.add/set/remove/get source code exploration

    add:

    private transient volatile Object[] array;//volatile确保数组的可见性
    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);//重新拷贝一份新数组,长度+1
            newElements[len] = e;//元素加入新数组
            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;//指定位置的元素更新为新元素
                setArray(newElements);//引用重新指向
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);//旧元素和新元素一致
            }
            return oldValue;//返回指定位置的旧元素
        } finally {
            lock.unlock();
        }
    }

   remove:

    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];//创建新数组,长度-1
                System.arraycopy(elements, 0, newElements, 0, index);//复制指定位置前面的数据
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);//复制指定位置后面的数据
                setArray(newElements);//数组引用重新指向
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

   get: As you can see, there is no lock, and the element at the specified position is returned directly

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

   CopyOnWriteArrayList explores:

  1. Comparison between CopyOnWriteArrayList and Vector: Each method of Vector is synchronized, which is much better than CopyOnWriteArrayList, which only locks write operations;
  2. CopyOnWriteArrayList is suitable for concurrent scenarios that read more and write less, such as configuration, whitelist, blacklist, access and update scenarios of commodity categories, logistics addresses and other data with very little change;
  3. CopyOnWriteArrayList has a memory problem, that is, each write operation needs to copy and replace resources. If the resource object occupies too much memory, it may cause frequent Yong GC and Full GC, which will cause the program's response time to become longer;
  4. CopyOnWriteArrayList tries to use the batch add operation addAll method;
  5. The CopyOnWrite container can only guarantee the final consistency of the data, but cannot guarantee the real-time consistency of the data.

  CopyOnWriteArraySet

  A Set uses the internal CopyOnWriteArrayList for all its operations.

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {

    private final CopyOnWriteArrayList<E> al;

    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
    public boolean add(E e) {
        return al.addIfAbsent(e);
    }
}
public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    public boolean addIfAbsent(E e) {//如果元素已经存在,返回false,否则进行写操作(CopyOnWrite)
        Object[] snapshot = getArray();
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
}

Guess you like

Origin blog.csdn.net/weixin_45536242/article/details/125780880