Background
Копирование при записи (COW) иногда называют «неявным совместным использованием», как следует из названия, которое позволяет всем пользователям, которым необходимо использовать ресурс R, совместно использовать одну и ту же копию ресурса R, когда один из пользователей хочет использовать ресурс R. операцию модификации, сначала копируют копию R' из R, а затем выполняют операцию модификации;
Problem
В Java Collections Framework что-то вродеArrayList
, HashSet
Такие базовые классы коллекций не являются потокобезопасными, и в многопоточной среде могут выполняться одновременные операции обхода и модификации.ConcurrentModificationException
; Для решения этой проблемы можно выполнить синхронизацию каждой операции, но синхронизация коллекции, в которой большинство операций считывают данные, может резко снизить производительность, и в этом случае снижение производительности не требуется;
Например, в следующих координатах ось X представляет ось времени, а ось Y представляет разные потоки.+
представляет операцию чтения,*
Представляет операцию модификации:
| ++ ++ ++ ++ ++ ++ ++
|++ + ++ + ++ + +++ + +
| ++ ++ * ++ +* ++ +
| ++ + + * ++ + ++ +
|+ + +++ * + + +++ + ++
| + + + + + + + + +
+----------------------
1 2 3
За исключением трех моментов времени 1 2 3, остальные моменты времени являются только операциями чтения, и нет необходимости в синхронизации в моменты времени, отличные от 1 2 3;
Поэтому мы хотим использовать технику для работы с ресурсами, которые имеют гораздо больше операций чтения, чем операций модификации в многопоточной среде, чтобы не было необходимости синхронизировать каждую операцию;
Solution
Библиотека классов Java предоставляет два класса Copy-on-Write:CopyOnWriteArrayList
иCopyOnWriteArraySet
, соответственно, достигнутоList
иSet
два интерфейса;
How CopyOnWriteArrayList Works
CopyOnWriteArrayList
Синхронизация выполняется только при его изменении, поэтому егоadd
, remove
механизмы синхронизации используются в таких методах, какCopyOnWriteArrayList
, определяет повторную блокировку:
final transient ReentrantLock lock = new ReentrantLock();
Эта блокировка используется во всех методах, которые изменяют коллекцию (add
, remove
и т. д.) для синхронизации, когда выполняется фактическая операция модификации, исходный массив будет сначала скопирован, затем изменен и, наконец, заменен оригиналом:
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();
}
}
Поскольку копия данных копируется при их изменении, все чтения не нужно синхронизировать:
public E get(int index) {
return get(getArray(), index);
}
Но это также приведет к проблеме «слабой согласованности»; так называемая «слабая согласованность» означает, что когда поток читает данные, если другой поток одновременно изменяет данные в этой области, читающий поток не будет способен читать последние данные, то есть поток чтения может читать только последние данные до времени чтения;
Другое проявление «слабой согласованности» заключается в том, что при использовании итератора, при использовании итератора для обхода коллекции, итератор может перейти только к данным, когда итератор был создан, а для модификации коллекции после создания итератора, итератор не может воспринимать; это потому, что когда итератор создается, итератор создает «моментальный снимок» исходных данных; поэтомуCopyOnWriteArrayList
иCopyOnWriteArraySet
Его можно применять только к сценариям, не требующим высоких данных в реальном времени;
How CopyOnWriteArraySet Works
CopyOnWriteArraySet
реализация основана наCopyOnWriteArrayList
, который внутренне поддерживаетCopyOnWriteArrayList
Примерal
:
private final CopyOnWriteArrayList<E> al;
public CopyOnWriteArraySet() {
al = new CopyOnWriteArrayList<E>();
}
все парыCopyOnWriteArraySet
операции делегируютсяal
, какadd
метод:
public boolean add(E e) {
return al.addIfAbsent(e);
}
очень типичноКомбинированный режимприменение;