Получение блокировок для операции добавления в CopyOnWriteArrayList

#java #multithreading #reentrantlock

#java #многопоточность #повторная блокировка

Вопрос:

Зачем нам нужно получать Reentrant блокировку в соответствии с приведенным ниже кодом в CopyOnWriteArrayList , когда мы добавляем элементы в List . Мы создаем копию исходного массива, а затем модифицируем его. Какие побочные эффекты мы можем иметь, если мы не получим lock в первую очередь?

 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();
        }
    }
 

Ответ №1:

Когда вы пытаетесь выполнить какую-либо операцию с глобальной переменной в многопоточном контексте и хотите, чтобы она была как атомарной, так и обеспечивала видимость памяти для других потоков, вам необходимо иметь блокировку вокруг этой операции.

Здесь getArray() возвращается глобальное поле экземпляра Object[] array .

Итак, в этом примере:

 Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len   1);
newElements[len] = e;
setArray(newElements);
return true;
 

Если вокруг этого блока кода нет блокировки и предположим, что два потока пытаются добавить элемент, в этом случае может случиться так, что поток один и поток два оба считывают одно и то же значение len и присваивают новый элемент одному и тому же индексу.

Итак, какой бы поток ни назначил новое значение в конце, он перезапишет значение, установленное ранее другим потоком.

Чтобы объяснить дальше, скажем, что и поток один, и поток два считывают одно и то же значение len теперь поток один продолжает создавать новый массив Arrays.copyOf(elements, len 1) и присваивает значение переменной e в len позиции нового массива.

И перед потоком можно установить новый массив, используя setArray(newElements) второй поток, тем временем продолжая этот процесс с тем же значением len . Хотя это создаст новый экземпляр массива, но индекс, в котором установлен новый элемент, будет таким же, как len используемый первым потоком.

Таким образом, когда поток two использует setArray(newElements) для установки нового массива с новым значением после потока one, более раннее значение массива в индексе len th будет перезаписано новым элементом, установленным потоком two.