Введение в поточно-ориентированный CopyOnWriteArrayList

Java задняя часть Безопасность

Докажите, что CopyOnWriteArrayList является потокобезопасным.


Напишите кусок кода, чтобы доказатьCopyOnWriteArrayListЭто действительно потокобезопасно.

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);
    }
}

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();
    }
}

Выполнение приведенного выше кода не сообщает

java.util.ConcurrentModificationException

Это показывает, что CopyOnWriteArrayList все еще может хорошо работать в параллельной многопоточной среде.


Как CopyOnWriteArrayList является потокобезопасным


CopyOnWriteArrayListс помощью метода, называемогокопирование при записиметод, когда новый элемент добавляется вCopyOnWriteArrayListКогда , сначала скопируйте копию из исходного массива, затем выполните операцию записи в новый массив и после записи укажите исходную ссылку массива на новый массив.

Когда добавляется новый элемент, как показано на рисунке ниже, создается новый массив, и в новый массив добавляется новый элемент.В это время ссылка массива по-прежнему указывает на исходный массив.

这里写图片描述
напишите сюда описание фото


Когда элемент успешно добавлен в новый массив, ссылка на эту ссылку указывает на новый массив.

这里写图片描述
напишите сюда описание фото

CopyOnWriteArrayListВся операция добавления находится вЗамокпод защитой.
Это сделано для того, чтобы избежать многопоточного параллельного добавления,сделать несколько копий, искажая данные, в результате чего окончательные данные массива не соответствуют нашим ожиданиям.

CopyOnWriteArrayListизaddИсходный код операции выглядит следующим образом:

 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();
    }
}

Поскольку все операции записи выполняются в новый массив, в это время, если есть параллельные записи потоками, они контролируются блокировками, а если есть параллельные чтения потоками, то возможны несколько ситуаций:
1. Если операция записи не завершена, прочитать данные исходного массива напрямую;
2. Если операция записи завершена, но ссылка еще не указала на новый массив, то также считываются данные исходного массива;
3. Если операция записи завершена и ссылка указывает на новый массив, то читаем данные непосредственно из нового массива.

видимый,CopyOnWriteArrayListизоперация чтенияДа, ты можешьзамокиз.


Сценарии использования CopyOnWriteArrayList


Благодаря приведенному выше анализу,CopyOnWriteArrayListЕсть несколько недостатков:
1. Из-за необходимости копировать массив во время операции записи он будет потреблять память.Если содержимое исходного массива относительно велико, это может привести кyoung gcилиfull gc

2, не может быть использован дляЧитать в режиме реального времениВ таких сценариях, как копирование массива и добавление новых элементов, требуется время, поэтому вызовитеsetПосле операции считанные данные могут быть все еще старыми, хотяCopyOnWriteArrayListможет сделать этовозможная согласованность, но все еще не может удовлетворить требования в реальном времени;

CopyOnWriteArrayListподходящееБольше читайте и меньше пишитесцена, но используйте этот тип с осторожностью
Потому что никто не может гарантироватьCopyOnWriteArrayListСколько данных класть в итоге, если данных чуть больше, то массив нужно перекопировать каждый раз при добавлении/установке, это слишком высокая стоимость. В высокопроизводительных интернет-приложениях эта операция может привести к сбою за считанные минуты.


Идеи, раскрытые CopyOnWriteArrayList


Некоторые идеи, выраженные CopyOnWriteArrayList в приведенном выше анализе:
1. Разделение чтения и письма, разделение чтения и письма
2. Конечная согласованность
3. Используйте идею открытия пространства для решения конфликтов параллелизма


Оригинальная ссылка