Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Java Concurrent Do you know CopyOnWrite?


Jun 01, 2021 Article blog


Table of contents


conception

What CopyOnWrite is, literally, is copying on write. It looks very simple, so how exactly is replication done when writing?

First of all, talk about thought, how to achieve and so on analysis

CopyOnWrite idea is that when you add an element to a container, instead of adding it directly to the current container, you copy it out of a new container, add elements inside the new container, add the reference to the original container, and then point the reference to the original container to the new container, which enables write-time replication

Do you remember when you talk about databases, you usually say that the master is separated from replication, reading and writing? CopyOnWrite design ideas are not as detached from what is often said as master-to-copy, read-write separation?

(Recommended tutorial: Java tutorial)

Advantages and disadvantages

Once you understand the concept, you should have a better understanding of its advantages and disadvantages

The advantage is that read and write can be performed in parallel, because the original container is read, the new container is written, they do not affect each other, so reading and writing can be performed in parallel, in some high concurrent scenarios, you can improve the response time of the program

But, as you can see, CopyOnWrite copied a new container out at the time of writing, so consider its memory overhead and go back to the idea that has been emphasized in learning algorithms: swap space for time

It is important to note that it only guarantees the final consistency of the data. Because at the time of reading, the contents of the read are the contents of the original container, and the newly added content is unreadable

Based on its advantages and disadvantages, one conclusion should be drawn: CopyOnWrite is suitable for scenarios where there are very few writes, and can tolerate temporary inconsistencies in read and write If your scenario isn't suitable, consider using something else

It is also important to note that when writing, it copies a new container, so if there is a write requirement, it is best to write in bulk, because each time you write, the container is copied, and if you can reduce the number of writes, you can reduce the number of copies of the container

Under the JUC package, CopyOnWrite ideas are implemented by CopyOnWriteArrayList and CopyOnWriteArraySet and this article focuses on making it clear that CopyOnWriteArrayList

CopyOnWriteArrayList

In CopyOnWriteArrayList it is important to note the add method:

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        // 在写入的时候,需要加锁,如果不加锁的话,在多线程场景下可能会被 copy 出 n 个副本出来
        // 加锁之后,就能保证在进行写时,只有一个线程在操作
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            // 复制原来的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 将要添加的元素添加到新数组中
            newElements[len] = e;
            // 将对原数组的引用指向新的数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

Locks are required when writing, but not when read

Because the element of the original array is read, there is no effect on the new array, and locking increases performance overhead

public E get(int index) {
 return get(getArray(), index);
}

For example:

CopyOnWrite is under the JUC package, so it keeps the thread safe

Let's do a little demo verification:

@Slf4j
public class ArrayListExample {


    // 请求总数
    public static int clientTotal = 5000;


    // 同时并发执行的线程数
    public static int threadTotal = 200;


    private static List<Integer> list = new ArrayList<>();


    public static void  main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}",list.size());
    }
    private static void update(int i){
        list.add(i);
    }
}

It's 5000 client requests, 200 threads are requesting at the same time, I'm using ArrayList implementation, let's look at the print results:

 Java Concurrent Do you know CopyOnWrite?1

If it's thread-safe, the end result should be 5000, and run a few more times and you'll find that each program executes differently

What if it's CopyOnWriteArrayList

@Slf4j
public class CopyOnWriteArrayListExample {


    // 请求总数
    public static int clientTotal = 5000;


    // 同时并发执行的线程数
    public static int threadTotal = 200;


    private static List<Integer> list = new CopyOnWriteArrayList<>();


    public static void  main(String[] args) throws Exception{
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal; i++) {
            final int count = i;
            executorService.execute(()->{
                try {
                    semaphore.acquire();
                    update(count);
                    semaphore.release();
                } catch (Exception e) {
                    log.error("excepiton",e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("size:{}",list.size());
    }
    private static void update(int i){
        list.add(i);
    }
}

Run a few more times and the results are the same:

 Java Concurrent Do you know CopyOnWrite?2

Thus, CopyOnWriteArrayList is thread-safe.

(Recommended micro-class: Java micro-class)

Source: Java Geek Technology

Author: Duck Blood Fans

The above is about Java concurrent CopyOnWrite related to the introduction, I hope to help you.