Почему CopyOnWriteArrayList копирует при записи?

Из CopyOnWriteArrayList.java метод добавления выглядит следующим образом:

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

Нетрудно понять, что операция добавления должна блокироваться, меня смущает то, что она копирует старые данные в новый массив и отказывается от предыдущего. между тем метод get выглядит следующим образом:

public E get(int index) {
        return (E)(getArray()[index]);
    }

Без блокировки в методе get. Я нахожу некоторые объяснения, некоторые говорят, что копирование в новый массив может избежать добавления и получения метода, работающего с одним и тем же массивом. Моя проблема в том, почему два потока не могут одновременно читать и писать?


person bob zhou    schedule 24.09.2013    source источник
comment
stackoverflow.com /вопросы/17853112/   -  person Ankur Lathi    schedule 24.09.2013


Ответы (3)


Если вы просто посмотрите на начало класса CopyOnWriteArrayList о объявлении array ссылочной переменной, там есть ответ на ваш вопрос.

 private volatile transient Object[] array; // this is volatile

return (E)(getArray()[index]);

который возвращает последнюю копию array[index], так что это threadsafe

final Object[] getArray() {
      return array;
  }

getArray возвращает ссылку на array.

person amicngh    schedule 24.09.2013
comment
Кажется, я понял, спасибо. Другое дело, если бы я определил volatile int i = 1; поток A читать i, поток B i++; какой неправильный результат может произойти с потоком A? - person bob zhou; 24.09.2013
comment
Если вы пометили переменную i как volatile, то не будет никакого неправильного результата, так как volatile гарантирует, что последняя копия i - person amicngh; 24.09.2013
comment
Обратите внимание, что пометка ссылки как volatile не делает операции над ней атомарными. При использовании volatile int i = 1; i++ по-прежнему является неатомарной операцией чтение-изменение-запись, и выполнение ее из нескольких потоков может привести к потере обновлений. - person bowmore; 24.09.2013
comment
Пожалуйста, обратитесь к JLS docs.oracle .com/javase/specs/jls/se7/html/ - person amicngh; 24.09.2013

На самом деле причина блокировки пути записи заключается не в том, что ему необходимо обеспечить безопасность потоков с учетом пути чтения, а в том, что он хочет сериализовать писатели. Поскольку метод копирования при записи заменяет изменчивую ссылку, обычно лучше сериализовать эту операцию.

Ключом к этой идее является то, что запись выполняется путем копирования существующего значения, его изменения и замены ссылки. Из этого также следует, что однажды установленный объект, на который указывает ссылка, всегда только для чтения (т. е. никакая мутация не выполняется непосредственно для объекта, на который ссылается ссылка). Поэтому читатели могут безопасно получить к нему доступ без синхронизации.

Чтение и запись могут происходить одновременно. Однако подразумевается, что при чтении будет отображаться состояние скорого устаревания до тех пор, пока не будет выполнен набор изменчивых ссылок.

person sjlee    schedule 24.09.2013

Во время get(), если несколько потоков попытаются получить из списка, у них не будет проблем. Потому что из-за массива volatile он всегда будет читать последнюю копию и возвращать элемент из массива.

Но

Во время add() или set() каждый раз, когда они создают новый массив, чтобы избежать проблем с взаимным выполнением, это один из способов сделать объекты потокобезопасными, чтобы сделать неизменяемыми.

Если они использовали один и тот же объект массива во время добавления или установки, они должны сделать обход синхронизированным. В противном случае может возникнуть исключение, если какой-либо поток добавляет/удаляет объект в список во время обхода

Согласно документу Java

Поточно-ориентированный вариант java.util.ArrayList, в котором все изменяющие операции (добавление, установка и т. д.) реализуются путем создания новой копии базового массива.

Обычно это слишком дорого, но может быть более эффективным, чем альтернативы, когда операций обхода значительно больше, чем мутаций, и полезно, когда вы не можете или не хотите синхронизировать обходы.

Посмотреть это

package com.concurrent;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list=new CopyOnWriteArrayList<>();


        Viewer viewer=new Viewer();
        viewer.setList(list);       
        Thread t1=new Thread(viewer);

        Adder adder=new Adder();
        adder.setList(list);

        Thread t=new Thread(adder);
        t.start();
        t1.start();

    }

    static class Adder implements Runnable{

        private List<Integer> list;
        public void setList(List<Integer> list) {
            this.list = list;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                list.add(i);
                System.out.println("Added-"+i);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }

    }

    static class Viewer implements Runnable{

        private List<Integer> list;
        public void setList(List<Integer> list) {
            this.list = list;
        }
        @Override
        public void run() {
            while (true) {
                System.out.println("Length of list->"+list.size());
                for (Integer i : list) {
                    System.out.println("Reading-"+i);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }

        }

    }
}
person Ankit Katiyar    schedule 13.05.2014