Пересмотр очередей в Java

Java задняя часть
Пересмотр очередей в Java

«Это 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

Вывод в конце статьи

  1. synchronizedПроизводительность ключевых слов была относительно низкой до JDK 8. Вы можете увидеть код синхронизации, реализованный после JDK1.5, многие из которых реализованы черезReentrantLockосуществленный.
  2. В многопоточных сценариях помимо синхронизации необходимо также учитывать видимость данных.volatileРеализация ключевого слова.
  3. ArrayListНет синхронизации вообще, не потокобезопасно
  4. CopyOnWriteArrayListа такжеSynchronizedListпринадлежат потокобезопасной очереди
  5. CopyOnWriteArrayListРеализуйте разделение чтения и записи, подходящее для сценариев, когда нужно меньше писать и больше читать.
  6. SynchronizedListДанные должны быть строго согласованными, что является методом глобальной блокировки очереди, и операция чтения также будет заблокирована.
  7. VectorПросто производительность обхода итератора очень низкая, если не учитывать глобальную очередь блокировок, то производительность операций чистого чтения и отдельных операций записи такая же, какSynchronizedListнет большой разницы.

Рекомендуемое чтение


Привет, я смотрю на гору. Плавайте в мире кода, играйте и наслаждайтесь жизнью. Если статья была вам полезна, ставьте лайк, добавляйте в закладки и подписывайтесь. Приглашаем обратить внимание на паблик-аккаунт «Глядя на горную хижину» и открыть для себя другой мир.