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