Сбор списков небезопасен в многопоточных параллельных условиях, как это решить?

Java
Сбор списков небезопасен в многопоточных параллельных условиях, как это решить?

предисловие

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

Во-первых, в коллекции List используется имитация параллельного тестирования.

1.1 В однопоточной среде

public static void main(String[] args) {
    // List集合
    List<String> list = new ArrayList<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        list.add(UUID.randomUUID().toString().substring(0,5));
        System.out.println(list);
    }
}

在这里插入图片描述Видно, что в однопоточных условиях у нас нет проблем с выполнением операций вставки списка.Давайте смоделируем выполнение в параллельных условиях, какие проблемы возникнут.

1.2 В многопоточной среде

public static void main(String[] args) {
    // List集合
    List<String> list = new ArrayList<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

在这里插入图片描述

Когда ArrayList повторяется, если он изменяется одновременно, он выдает исключение java.util.ConcurrentModificationException, которое является исключением одновременной модификации.

2. Решения

2.1 Использование класса Vector

public static void main(String[] args) {
    // List集合
    List<String> list = new Vector<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

Доступ к вектору осуществляется синхронно, а его метод добавления украшен ключевым словом synchronized внизу.在这里插入图片描述Результаты теста:

在这里插入图片描述

2.1 Использование Collections.synchronizedList

public static void main(String[] args) {
   // List集合
    List<String> list = Collections.synchronizedList(new ArrayList<>());
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

Глядя на базовый исходный код, вы можете обнаружить, что он также использует модификацию ключевого слова synchronized.在这里插入图片描述

2.3 Использование параллельного контейнера CopyOnWriteArrayList

public static void main(String[] args) {
    // List集合
    List<String> list = new CopyOnWriteArrayList<>();
    // 循环插入
    for (int i = 0; i < 10; i++) {
        // 开启线程执行
        new Thread(()->{
            list.add(UUID.randomUUID().toString().substring(0,5));
            System.out.println(list);
        },"线程List").start();

    }
}

Просмотрите исходный код, в котором используется механизм блокировки замка.在这里插入图片描述Копирование при записи, когда вызывается несколько потоков, делайте копию при записи, чтобы избежать проблем с данными, вызванных перезаписью. То есть при записи исходный набор не модифицируется, а снова делается копия, после модификации указатель перемещается.

Начиная с JDK1.5, параллельный пакет Java предоставляет два параллельных контейнера, реализованных с помощью механизма CopyOnWrite: CopyOnWriteArrayList и CopyOnWriteArraySet. Контейнер CopyOnWrite очень полезен и может использоваться во многих параллельных сценариях.

Интерпретируйте исходный код:

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return {@code true} (as specified by {@link Collection#add})
 */
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);//拷贝新数组
        newElements[len] = e;
        setArray(newElements);//将引用指向新数组
        return true;
    } finally {
        lock.unlock();//解锁
    }
}

add() добавляет блокировку при добавлении коллекции, чтобы обеспечить синхронизацию и избежать копирования N копий при многопоточной записи.

Суммировать

Сценарий использования CopyOnWriteArrayList: больше читать и меньше писать (белый список, черный список, сценарии доступа и обновления товарных категорий), а коллекция не большая. В общем, мы будем использовать потокобезопасный контейнер, предоставляемый пакетом JUC, вместо использования потокобезопасного контейнера старого поколения.