写作时为什么CopyOnWriteArrayList复制?

时间:2013-09-24 05:54:48

标签: java multithreading collections copy-on-write copyonwritearraylist

从CopyOnWriteArrayList.java中,add方法如下:

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方法没有锁定。 我找到了一些解释,有人说复制到新数组可以避免add和get方法在同一个数组上运行。 我的问题是为什么两个线程不能同时读写?

3 个答案:

答案 0 :(得分:2)

如果您只是查看课程CopyOnWriteArrayList关于array referance变量声明的顶部,那么您的问题就会得到答案。

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

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

返回array[index]的最新副本,因此这是threadsafe

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

getArray正在返回对array的引用。

答案 1 :(得分:0)

实际上写路径锁定的原因不是因为考虑到读取路径需要提供线程安全性,而是因为它想要序列化编写器。由于写时复制技术取代了易失性引用,因此通常最好对该操作进行序列化。

这个想法的关键是通过复制现有值,修改它和替换引用来完成写入。此外,一旦设置,引用指向的对象始终是只读(即,不直接对引用引用的对象进行突变)。因此,读者可以安全地访问它而无需同步。

读取和写入可以同时发生。但是,这意味着读取将在易失性参考集完成之前看到即将过时的状态。

答案 2 :(得分:0)

在get()时,如果多个线程试图从列表中获取它们将没有问题。 因为 volatile 数组,它将始终读取最新的副本并从数组中返回该元素。

但是

在add()或set()期间,每次创建新数组以避免相互执行问题时,这是使对象线程安全的一种方法,使其不可变。

如果他们在添加或设置期间使用了相同的数组对象,那么他们必须使遍历同步。如果任何线程在遍历期间添加/删除对象列表,它可能会抛出异常

根据java doc

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

        }

    }
}