CopyOnWrite类解析

CopyOnWrite
  • 顾名思义,写时复制, 即做Write更新的操作时,进行复制。
  • 那么为什么要这样子做呢? 接下来我们以CopyOnWriteArrayList的源码来做分析。
CopyOnWriteArrayList
  • CopyOnWriteArrayList可以看成是ArrayList的线程安全版本,所以很多方面与ArrayList相同,我们略过一些相同的方面,先来看其主要属性
    /** 可重入锁,用来保证写安全 */
    transient final ReentrantLock lock = new ReentrantLock();

    /**  volatile数组.确保 set的时候修改引用地址的时候是原子操作 */
    private volatile transient Object[] array;
  • 两个属性,一个是数据, 一个是可重入锁来控制写。看一下构造函数。
    /**
     * 通过 set一个空Object数组进行初始化
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * 对于非Object类的, 转换为Object数组.再set
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
        setArray(elements);
    }

    /**
     * 将传入的array复制为 Object数组
     */
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
  • 构造函数都比较简单, 就是set一个 Object数组,再看一下几个读操作
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
    public E get(int index) {
        return get(getArray(), index);
    }
    /**
     */
    public int indexOf(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length);
    }
    /**
     * 通过遍历数组来确定位置. 
     */
    private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }
     /**通过indexOf来确定是否包含.
     */
    public boolean contains(Object o) {
        Object[] elements = getArray();
        return indexOf(o, elements, 0, elements.length) >= 0;
    }    
  • 读方法都很简单, 获取数组之后进行相关数组读。
  • 再来看看几个比较典型的写方法, add、set、remove
    /**
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();    //加锁. 防止同时有两个写线程进入.
        try {
            Object[] elements = getArray(); // 得到array的引用. 
            int len = elements.length;     //判断 index是否合理.
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;       //定义新数组.
            int numMoved = len - index;   //需要移动的元素数量. 即.index之后的元素都需要移动一个位置.
            if (numMoved == 0)    //插到最后.
                newElements = Arrays.copyOf(elements, len + 1);  //copy原数组,并设置copy后的新数组长度为 原数组长度+1.
            else {
                newElements = new Object[len + 1];  //新数组比原数组多一个元素
                System.arraycopy(elements, 0, newElements, 0, index);   //copy前段.
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);  //copy index之后的后段.
            }
            newElements[index] = element;    //在目标位置设值.
            setArray(newElements);      //将 完成写操作之后的数组刷新到引用中.
        } finally {
            lock.unlock();   //写操作完毕解锁.
        }
    }
    public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);

            if (oldValue != element) {
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
    public E remove(int index) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            E oldValue = get(elements, index);
            int numMoved = len - index - 1;
            if (numMoved == 0)
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
  • 代码加上注释很清晰了, 需要注意的是:
    • 这里用到了 lock,目的是阻止多线程写。同一时间,写操作只能有一个,这个是毋庸置疑的。
    • 这里lock可以简单理解为, try到finally这段代码加了 synchronized , 这样子就保证了多个线程调用这些写操作时安全。
    • 另外一点, 则是这里用到了 Arrays.copyOfSystem.arraycopy 。 首先,Arrays.copyOf本质上, 调用的还是System.arrayCopy的方法, 而System.arrayCopy则是使用native代码(基于内存块的拷贝),从而使数组拷贝尽量快。
    • 至于add方法, if 里使用Arrays.copy而else使用System.arraycopy而不使用 Arrays.copyRange。 我认为是因为在Arrays.copyRange里加了一些范围之类的检查,而在这里是没有必要的,因此这里使用System.arraycopy可以减少检查,效率更高.
  • 另外这里还有一个我之前想不明白, 既然已经保证了写的安全为什么要用copy, 而不在 getArray得到的数组里直接修改就好了。已经"安全"了,还复制有必要吗?
    • 其实,这里如果不用copy只用了写lock, 只能保证写的安全,如果不用copy读是不安全的
    • 例如,迭代器正在迭代,然后就被删除了一个,那么会有问题。
    • 另外,写元素的时候是非原子操作的,而读是没有lock的(但是lock了话就和VectorCollections.synchronizedList没有太大区别了),如果不copy, 会造成同时读写有问题。
    • 这就是为什么写已经安全了还要用copy的原因,是为了保证读也是安全的。
简单整理一下

在上面,我们发现, 写的时候不单有锁,并且还会进行copy, 可以发现:

1,写操作效率其实很低, 对于数据量比较大, 并且写操作比较频繁的场景是很不合适的。
2,读和写其实是分隔开了的, 除了写在同步时(setArray), 这两种操作不会互相影响。因此,写时复制在多线程频繁读的场景是比较合适的。在这种情景,该类的效率是大于Vector的, 读线程越多,表现越明显

另外,CopyOnWrite这里还有一个类, CopyOnWriteArraySet不重复元素的集合, 其底层其实使用 CopyOnWriteArrayList, 所以基本和CopyOnWriteArrayList一样的, 可以自行浏览一下源码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值