java源码阅读——CopyOnWriteArrayList和CopyOnWriteArraySet

2021/7/22 14:08:10

本文主要是介绍java源码阅读——CopyOnWriteArrayList和CopyOnWriteArraySet,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

目录

  • CopyOnWriteArrayList
    • 迭代
    • 属性
    • 变更操作
  • CopyOnWriteArraySet


在这里插入图片描述

CopyOnWriteArrayList

CopyOnWriteArrayList时ArrayList的一种线程安全变体,它的名字前面有个CopyOnWrite,这个名字就表明了它的操作特点,它的所有变动操作(新增add,设置set,删除remove等等)都是会先复制(copy)一个副本,然后再副本上进行相应操作,最后将副本写(write)回存储

CopyOnWriteArrayList的变动操作一般性的流程是:
1、加锁
2、复制底层的存储数组
3、在复制的数组上进行变动操作(add,set,remove等)
4、调用setArray方法,将复制出来的新数组进行存储并覆盖原数组
5、解锁

这种方式需要比较大的代价,但是当迭代操作的数量大大超过变更操作的数量时,可能会比其他方案更加有效,特别是当你不想或者不能对迭代操作进行同步,且要保证线程安全时,可以使用这种方案

迭代

关于这个类,我们先来看一看它的迭代操作

跟以前一样,定义了迭代器私有类来执行迭代操作,比较不一样的地方是,COWIterator在初始化的时候,会生成一个当前数组的快照,并且迭代操作就是在这个快照上进行的,所以和ArrayList不一样的地方是,在迭代过程中对数组的改动并不会影响迭代操作,并且该类保证永远不会抛出ConcurrentModificationException异常,毕竟是在快照上进行迭代,原本数组怎么变都没啥关系

static final class COWIterator<E> implements ListIterator<E> {
    /** 数组的快照 */
    private final Object[] snapshot;
    /** 迭代索引,指向下一个要遍历的元素  */
    private int cursor;

    COWIterator(Object[] es, int initialCursor) {
        cursor = initialCursor;
        snapshot = es;
    }
}

属性

该类的底层也是用一个对象数组进行存储,并且用了volatile关键字进行修饰

/** 锁对象 */
final transient Object lock = new Object();

/** 存储数据的底层数组,只能通过set/get方法访问 */
private transient volatile Object[] array;

底层数组的set和get,在进行变更操作时经常要用到

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

// 设置数组
final void setArray(Object[] a) {
    array = a;
}

变更操作

设置set操作

public E set(int index, E element) {
	// 加锁
    synchronized (lock) {
    	// 获取原数组
        Object[] es = getArray();
        // 获取原值
        E oldValue = elementAt(es, index);
		// 如果值不相等,那么需要进行值的覆盖
        if (oldValue != element) {
        	// 对数组进行复制
            es = es.clone();
            // 在新数组上进行操作
            es[index] = element;
        }
        // 将新数组写回
        setArray(es);
        return oldValue;
    }
}

增加add操作

public void add(int index, E element) {
	// 加锁同步
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        // 判断索引是否越界
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        // 构建新数组
        Object[] newElements;
        // 判断添加的位置是否是数组的最后一个位置
        int numMoved = len - index;
        // 如果是的话,复制一个长度为len+1的数组(因为要添加元素,所以长度要加1)
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, len + 1);
        // 如果不是的话,新建len+1长度的数组
        else {
            newElements = new Object[len + 1];
            // 复制[0,index)位置的数据
            System.arraycopy(es, 0, newElements, 0, index);
            // 复制[index+1,len)位置的数据
            System.arraycopy(es, index, newElements, index + 1,
                             numMoved);
        }
        // 将index位置上的数据设置为添加的数据
        newElements[index] = element;
        // 将新建的数组写回
        setArray(newElements);
    }
}

该类中的读取操作并没有用到同步操作,和ArrayList类似

CopyOnWriteArraySet

CopyOnWriteArraySet是一个底层使用了CopyOnWriteArraySet进行所有操作的set容器,因此它具有CopyOnWriteArrayList的属性

它比较适合于读操作远远大于写操作的应用程序,且需要在进行迭代操作的时候不会受到其他线程写数据的干扰

它主要使用addIfAbsent这个函数保证了数据的唯一性,在添加数据的时候,首先会读取一个数据的快照,然后在这个快照上查找是否有重复元素,如果没有元素重复,那么就会添加新元素

public boolean add(E e) {
    return al.addIfAbsent(e);
}

public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray();
    return indexOfRange(e, snapshot, 0, snapshot.length) < 0
        && addIfAbsent(e, snapshot);
}

至于其他的一些操作,基本上就是调用CopyOnWriteArrayList的Api



这篇关于java源码阅读——CopyOnWriteArrayList和CopyOnWriteArraySet的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程