0

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

不难理解 add 操作应该加锁,让我困惑的是它将旧数据复制到新数组并放弃前一个。同时get方法如下:

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

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

4

3 回答 3

2

CopyOnWriteArrayList如果您只是查看关于引用变量声明的类的顶部,那么array 您的问题的答案。

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

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

它返回最新的副本,array[index]所以这是threadsafe

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

getArray正在返回对 的引用array

于 2013-09-24T06:16:06.547 回答
1

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

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

读取和写入可以同时发生。然而,这意味着读取将看到即将失效的状态,直到 volatile 引用集完成。

于 2013-09-24T15:54:21.157 回答
0

在 get() 时,如果多个线程尝试从列表中获取它们将没有问题。因为由于易失性数组,它总是会读取最新的副本并从数组中返回元素。

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

        }

    }
}
于 2014-05-13T12:34:52.467 回答