【问题标题】:Why CopyOnWriteArrayList copys when writing?为什么 CopyOnWriteArrayList 在写入时复制?
【发布时间】:2013-09-24 05:54:47
【问题描述】:

从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 方法在同一个数组上操作。 我的问题是为什么两个线程不能同时读写?

【问题讨论】:

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


【解决方案1】:

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

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

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

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

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

getArray 正在返回对array 的引用。

【讨论】:

  • 我想我明白了,谢谢。另一件事,如果我定义了 volatile int i = 1;线程 A 读取 i,线程 B i++;线程A会发生什么样的错误结果?
  • 如果您将变量 i 标记为 volatile 则不会有任何错误结果,因为 volatile 确保 i 的最新副本
  • 请注意,将引用标记为 volatile 不会使其上的操作成为原子操作。对于volatile int i = 1;i++ 仍然是一个非原子的 read-modify-write 操作,并且从多个线程执行它可能会导致更新丢失。
【解决方案2】:

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

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

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-12
      • 1970-01-01
      • 2016-07-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多