«Это 18-й день моего участия в ноябрьском испытании обновлений, ознакомьтесь с подробностями события:Вызов последнего обновления 2021 г."
Эта статья была«Путь развития Java от маленького работника до эксперта»записано.
Привет, я смотрю на гору.
Продолжая книгу, мы говорили о ней в прошлый разЧто происходит при использовании ArrayList в многопоточности, в этот раз мы говорим о часто используемых списках: Vector, ArrayList, CopyOnWriteArrayList, SynchronizedList.
Vector
Vector
предоставляется в JDK 1.0, хотя и не отмеченDeprecated
, а на самом деле им уже никто не пользуется. Основная причина – низкая производительность и несоответствие требованиям.
Как видно из исходного кода (исходный код здесь не выложен),Vector
Он реализован на основе массивов и используется практически во всех методах работы.synchronized
Ключевое слово реализует синхронизацию методов, которая может блокировать одну операцию, например, несколько потоков, выполняемых одновременно.add
Синхронное блокирующее выполнение, но многопоточное выполнениеadd
а такжеremove
, он не будет блокировать.
Однако в большинстве сценариев, требующих блокировки очереди, блокируется вся очередь, а не только одна операция. То есть,Vector
Это отличается от наших ожиданий, но также увеличивает нагрузку на производительность, вызванную операцией синхронизации. Поэтому не обязательно использовать сцену, можно использоватьArrayList
Вместо этого, даже если вам нужно синхронизировать очередь в многопоточном случае, вы можете использоватьCopyOnWriteArrayList
а такжеSynchronizedList
заменять.
ArrayList
ArrayList
предоставляется в JDK 1.1 какVector
преемник (ArrayList
реализация иVector
почти идентичны)ArrayList
поставить методsynchronized
Все удалено, синхронизация вообще не достигается, и это не потокобезопасно.
Его непотоковая безопасность также отражается в отказоустойчивости итераторов. как пользоватьсяiterator
а такжеlistIterator
После создания итератора, если исходныйArrayList
Когда очередь изменяется (добавляется или удаляется), она сообщит, когда итератор выполняет итерацию.ConcurrentModificationException
аномальный. Как видно из исходного кода, итератор будет проверять количество модификаций в очереди в процессе итерации.modCount
Снапшот с количеством модификаций, выпавших при создании итератораexpectedModCount
Является ли он равным? Равный означает, что он не был изменен. Код выглядит следующим образом:
private class Itr implements Iterator<E> {
// 这段代码是从 ArrayList 中摘取的
// 只留下检查方法,略过其他代码,有兴趣的可以从源码中查看
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
Третий момент заключается в том, что в многопоточном сценарии добавление элементов может привести к потере данных или возникновению исключения выхода за границы массива.Что происходит при использовании ArrayList в многопоточностиЕсть подробное описание, которое здесь не повторяется.
SynchronizedList
SynchronizedList
даCollections
статический внутренний класс, используяCollections.synchronizedList()
Создание статического метода представляет собой комбинациюList
Инкапсулированная реализация реализации класса. Большинство его методов проходятsynchronized (mutex){...}
Метод синхронизации блока кода, поскольку объект блокировкиmutex
тот же объект, определенный в объекте очереди, так что правильноmutex
При блокировке блокируется вся очередь, что решаетсяVector
Проблема невозможности заблокировать всю очередь. Так что, если есть несколько потоков, работающих одновременноadd
а такжеremove
метод, который блокирует синхронное выполнение.
ArrayList
Ситуация отказоустойчивости итератора, существующая в , все еще существует, как указано в исходном коде ниже: Чтобы использовать итераторы, пользователь должен выполнить синхронизацию вручную.
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
// 代码摘自 Collections,省略很多代码
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
}
При ручной синхронизации нам нужно обратить внимание, так как мы обеспокоены глобальной синхронизацией, когда итератор настроен на синхронизацию, мы должны убедиться, что объект блокировки иadd
Объекты в методе одинаковые. Это будет объяснено в последующем дополнении и не будет расширяться здесь.
CopyOnWriteArrayList
CopyOnWriteArrayList
Он предоставляется начиная с JDK 1.5, давайте посмотримadd
Исходный код метода:
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
// 代码摘自 CopyOnWriteArrayList,省略很多代码
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 boolean addAll(Collection<? extends E> c) {
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
System.arraycopy(cs, 0, newElements, len, cs.length);
setArray(newElements);
}
return true;
} finally {
lock.unlock();
}
}
private E get(Object[] a, int index) {
return (E) a[index];
}
/**
* {@inheritDoc}
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E get(int index) {
return get(getArray(), index);
}
}
можно увидеть,CopyOnWriteArrayList
с помощьюReentrantLock
добиться синхронизации, вsynchronized
Перед оптимизацией,ReentrantLock
производительность выше, чемsynchronized
.CopyOnWriteArrayList
Он тоже реализован через массив, но добавляется перед массивомvolatile
ключевое слово, реализующее видимость массива в случае многопоточности, что более безопасно. Важнее,CopyOnWriteArrayList
существуетadd
При добавлении элементов реализация состоит в том, чтобы перестроить объект массива, заменив исходную ссылку на массив. а такжеArrayList
По сравнению с методом расширения пространство уменьшается, но при этом увеличиваются издержки производительности массива присваивания. существуетget
При получении элемента блокировки нет, и данные возвращаются напрямую.
CopyOnWriteArrayList
итераторCOWIterator
реализовано, вызовiterator
метод, присваивает моментальный снимок массива в текущей очереди ссылке на массив в итераторе. Если исходная очередь будет изменена, массив в очереди будет указывать на другие ссылки, но массив в итераторе не изменится, поэтому в процессе многопоточного выполнения данные в очереди также могут быть изменены путем обхода массив через итератор. Хотя этот метод обеспечивает безопасность потоков, также может возникать несогласованность данных, поэтому вы можете только уделять больше внимания.
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
}
Сравните CopyOnWriteArrayList и SynchronizedList
CopyOnWriteArrayList
а такжеSynchronizedList
Все они синхронизированы, и в методе реализации используются разные стратегии, и их направленность различна.
CopyOnWriteArrayList
Сосредоточьтесь на разделении чтения-записи, происходят операции записи данных (add
илиremove
), он будет заблокирован, каждый поток будет блокировать выполнение, процесс выполнения создаст копию данных и заменит ссылку на объект; если при этом идет операция чтения (get
илиiterator
), операция чтения считывает старые данные, либо становится моментальным снимком исторических данных, либо становится кэшированными данными. Это приведет к несогласованности данных при одновременном чтении и записи, но в конечном итоге данные будут согласованными. Этот метод почти такой же, как режим разделения чтения и записи базы данных, и многие функции можно сравнить.
SynchronizedList
Сосредоточьтесь на строгой согласованности данных, то есть когда происходит операция записи данных (add
илиremove
), он будет заблокирован, и каждый поток будет блокировать выполнение, и он также будет блокироваться через ту же блокировкуget
работать.
отCopyOnWriteArrayList
а такжеSynchronizedList
Два разных взгляда на вещи, которые можно вывестиCopyOnWriteArrayList
Он имеет высокую эффективность выполнения в сценарии меньше писать и больше читать.SynchronizedList
Эффективность операций чтения и записи очень сбалансирована, поэтому эффективность выполнения будет выше, чем в сценарии «больше писать и читать меньше».CopyOnWriteArrayList
. Заимствуйте результаты теста из интернета:
Сравните CopyOnWriteArrayList и SynchronizedList
Вывод в конце статьи
-
synchronized
Производительность ключевых слов была относительно низкой до JDK 8. Вы можете увидеть код синхронизации, реализованный после JDK1.5, многие из которых реализованы черезReentrantLock
осуществленный. - В многопоточных сценариях помимо синхронизации необходимо также учитывать видимость данных.
volatile
Реализация ключевого слова. -
ArrayList
Нет синхронизации вообще, не потокобезопасно -
CopyOnWriteArrayList
а такжеSynchronizedList
принадлежат потокобезопасной очереди -
CopyOnWriteArrayList
Реализуйте разделение чтения и записи, подходящее для сценариев, когда нужно меньше писать и больше читать. -
SynchronizedList
Данные должны быть строго согласованными, что является методом глобальной блокировки очереди, и операция чтения также будет заблокирована. -
Vector
Просто производительность обхода итератора очень низкая, если не учитывать глобальную очередь блокировок, то производительность операций чистого чтения и отдельных операций записи такая же, какSynchronizedList
нет большой разницы.
Рекомендуемое чтение
- В JDK также есть константы интерфейса против шаблонов.
- На что следует обратить внимание при импорте пакета с помощью java import?
- Java Concurrency Foundation (1): синхронизированная синхронизация блокировки
- Java Concurrency Foundation (2): основной поток ожидает окончания дочернего потока
- Основы параллелизма в Java (3): снова поговорим о CountDownLatch
- Java Concurrency Foundation (4): снова о CyclicBarrier
- Java Concurrency Foundation (5): многопоточная последовательная печать на собеседованиях
- Что произойдет, если вам придется использовать ArrayList в нескольких потоках?
- Что произойдет, если вам придется использовать ArrayList в нескольких потоках? (Часть II)
- Пересмотр очередей в Java
- Разница между Vector и SynchronizedList в Java
- В этой статье описаны 24 операции Collectors в Java8 Stream.
- Статья для освоения 6 операций необязательного Java8.
Привет, я смотрю на гору. Плавайте в мире кода, играйте и наслаждайтесь жизнью. Если статья была вам полезна, ставьте лайк, добавляйте в закладки и подписывайтесь. Приглашаем обратить внимание на паблик-аккаунт «Глядя на горную хижину» и открыть для себя другой мир.