Introduction to thread-safe CopyOnWriteArrayList

Prove that CopyOnWriteArrayList is thread-safe

Write a piece of code to prove that CopyOnWriteArrayList is indeed thread-safe.

ReadThread.java

import java.util.List;

public class ReadThread implements Runnable {
    private List<Integer> list;

    public ReadThread(List<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        for (Integer ele : list) {
            System.out.println("ReadThread:"+ele);
        }
    }
}

WriteThread.java

import java.util.List;

public class WriteThread implements Runnable {
    private List<Integer> list;

    public WriteThread(List<Integer> list) {
        this.list = list;
    }

    @Override
    public void run() {
        this.list.add(9);
    }
}

Test program: TestCopyOnWriteArrayList.java

import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TestCopyOnWriteArrayList {

    private void test() {
        //1、初始化CopyOnWriteArrayList
        List<Integer> tempList = Arrays.asList(new Integer [] {1,2});
        CopyOnWriteArrayList<Integer> copyList = new CopyOnWriteArrayList<>(tempList);


        //2、模拟多线程对list进行读和写
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(new ReadThread(copyList));
        executorService.execute(new WriteThread(copyList));
        executorService.execute(new WriteThread(copyList));
        executorService.execute(new WriteThread(copyList));
        executorService.execute(new ReadThread(copyList));
        executorService.execute(new WriteThread(copyList));
        executorService.execute(new ReadThread(copyList));
        executorService.execute(new WriteThread(copyList));

        System.out.println("copyList size:"+copyList.size());
    }


    public static void main(String[] args) {
        new TestCopyOnWriteArrayList().test();
    }
}

Running the above code does not report

java.util.ConcurrentModificationException

It shows that CopyOnWriteArrayList can still work well in a concurrent multi-threaded environment.

How CopyOnWriteArrayList is thread-safe

CopyOnWriteArrayList uses a method called copy-on-write. When a new element is added to CopyOnWriteArrayList, a copy is first made from the original array, and then a write operation is performed on the new array. After writing, the original The array reference points to the new array.

When a new element is added, as shown in the figure below, a new array is created, and a new element is added to the new array. At this time, the reference of array still points to the original array.

Enter image description

When the element is added to the new array successfully, point the reference of array to the new array.

Enter image description

The entire add operation of CopyOnWriteArrayList is carried out under the protection of the lock. This is done to avoid copying out multiple copies when multi-threaded concurrent add, messing up the data, resulting in the final array data not what we expected.

The source code of the add operation of CopyOnWriteArrayList is as follows:

public boolean add(E e) {
    //1、先加锁
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        //2、拷贝数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //3、将元素加入到新数组中
        newElements[len] = e;
        //4、将array引用指向到新数组
        setArray(newElements);
        return true;
    } finally {
       //5、解锁
        lock.unlock();
    }
}

Since all write operations are performed in the new array, at this time, if there are concurrent writes by threads, they are controlled by locks. If there are concurrent reads by threads, there are several situations:

  • 1. If the write operation is not completed, read the data of the original array directly;
  • 2. If the write operation is completed, but the reference has not yet pointed to the new array, then the original array data is also read;
  • 3. If the write operation is completed and the reference has pointed to the new array, then read the data directly from the new array.

It can be seen that the read operation of CopyOnWriteArrayList does not need to be locked.

Usage scenarios of CopyOnWriteArrayList

Through the above analysis, CopyOnWriteArrayList has several disadvantages:

  • 1. Due to the need to copy the array during the write operation, it will consume memory. If the content of the original array is relatively large, it may cause young gc or full gc
  • 2. Scenarios that cannot be used for real-time reading, such as copying arrays and adding new elements take time, so after calling a set operation, the data read may still be old, although CopyOnWriteArrayList can achieve eventual consistency, it still cannot meet real-time requirements;

CopyOnWriteArrayList is suitable for scenarios that read more and write less, but use this type with caution

Because no one can guarantee how much data will be placed in CopyOnWriteArrayList, if there is a little more data, the array must be re-copied for each add/set, which is too expensive. In high-performance Internet applications, this operation can cause failures in minutes.

The ideas revealed by #CopyOnWriteArrayList are like some ideas expressed by CopyOnWriteArrayList in the above analysis:

  • 1. Separation of reading and writing, separation of reading and writing
  • 2. Eventual consistency
  • 3. Use the idea of ​​​​opening up space to solve concurrency conflicts

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325572581&siteId=291194637