CopyOnWriteArrayList

CopyOnWriteArrayList阅读笔记

一、简介

	CopyOnWriteArrayList是ArrayList的线程安全版本,内部也是通过数组实现,
每次对数组的修改都完全拷贝一份新的数组来修改,修改完了再替换掉老数组,这样保证了
只阻塞写操作,不阻塞读操作,实现读写分离。

二、继承关系图

在这里插入图片描述

三、存储结构

  • 底层采用List数组,锁采用ReentrantLock对写进行加锁,且每次修改都是直接copy一个新数组

四、源码分析

内部类

COWIterator、COWSubList、COWSubListIterator

属性
/** 用于修改时加锁:用transient修饰 */
final transient ReentrantLock lock = new ReentrantLock();
/** 用于存储元素的数组,只能通过getArray()方法和setArray()修改 */
private transient volatile Object[] array;
构造
/** 构造方法一:直接初始化一个长度为 0 的 Object数组 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
final void setArray(Object[] a) {
    array = a;
}

/**构造方法二:传入集合Collection对象来初始化*/
public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    // 如果是CopyOnWriteArrayList类型,则直接强转 赋值给临时数组elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    // 如果不是CopyOnWriteArrayList类型,则直接把集合转化为数组,再赋值给临时数组elements;
    else {
        elements = c.toArray();//集合转化为数组
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            //toArray()有可能不是Object数组,所以,如果不是Object数组,则进行数组copy为Object类型
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    // 把临时数组elments赋值给当前Object数组
    setArray(elements);
}

/** 构造方法三:直接copy参数toCopyIn的元素给当前的Object数组 */
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
主要方法

public boolean add(E e)

  • 添加元素,成功返回true,失败返回false,使用lock加锁
public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取数组元素
        Object[] elements = getArray();
        int len = elements.length;
        // 将旧元素拷贝到新数组中
        // 新数组大小是旧数组大小加 1
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 把元素加入到新数组的尾部
        newElements[len] = e;
        // 把新数组赋值给当前数组
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

public void add(int index, E element)

  • 在指定索引插入新的元素
public void add(int index, E element) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 获取旧元素
        Object[] elements = getArray();
        int len = elements.length;
        // 检查越界,index可以等于len
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            // 如果插入的位置是数组最后一尾的后面
            // 那么直接新建一个len + 1的数组,并copy旧数组到新数组
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            // 新建一个len + 1数组
            newElements = new Object[len + 1];
            // copy 旧数组index前面的到新数组
            System.arraycopy(elements, 0, newElements, 0, index);
            // copy 旧新书index后面的到新数组
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        // 把新的元素插入到index
        newElements[index] = element;
        setArray(newElements);
    } finally {
        // 释放锁
        lock.unlock();
    }
}

**public E set(int index, E element) **

  • 修改指定索引的元素
    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;
                // copy一个大小一样的新数组
                Object[] newElements = Arrays.copyOf(elements, len);
                // 替换掉原索引位的元素
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                // 如果没有变化就把元素set回去,这个是为了处理volatile的写语义问题,
                /*
                volatile修饰引用变量的问题即它只能保证引用本身的可见性,
                并不能保证内部字段的可见性,如果想要保证内部字段的可见性最好使用CAS的数据结构,
			   这里还需要说明的的一点是volatile有时候修饰引用类型如boolean数组<可能>结果是没问题的
                */
                setArray(elements);
            }
            // 返回旧元素
            return oldValue;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

public boolean addIfAbsent(E e)

  • 如果元素在数组中存在,返回false,不存在则添加元素,并返回true
public boolean addIfAbsent(E e) {
    // 取出旧数组
    Object[] snapshot = getArray();
    // 通过indexOf查询所有数组中是否有元素e,如果有则返回false,没有元素e 就触发addIfAbset函数
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
    addIfAbsent(e, snapshot);
}

/** 获得 o 的索引位置 */
private static int indexOf(Object o, Object[] elements,
                           int index, int fence) {
    if (o == null) {
        // 直接判断元素是否有null,找到第一个null返回其索引下标
        for (int i = index; i < fence; i++)
            if (elements[i] == null) 
                // 找到元素,返回索引下标
                return i;
    } else {
        // 元素不为null,则直接进行equals比较,找到第一个相等的元素返回其索引下标
        for (int i = index; i < fence; i++)
            if (o.equals(elements[i]))
                return i;
    }
    // 如果以上都没找到,返回-1
    return -1;
}

/**  */
private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
        // 重新获取旧数组
        Object[] current = getArray();
        int len = current.length;
        // 如果快照和刚获取的数组不一致,说明有修改
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            // 重新检查元素是否在刚获取的数组里
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    // 因为之前已经检查了,元素是不在快照中
                    // 但是重新获取的元素有不一样的
                    // 所以直接检查是否有不一样的元素,有的话,就再检查是否相同,相同则返回false
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                // 如果后面还有元素,则继续检查,找到则返回false;
                return false;
        }
        // 通过上面的方法,得出元素不存在新数组中,则开始元素copy 到n+1的新数组
        Object[] newElements = Arrays.copyOf(current, len + 1);
        // 添加元素到新数组的尾部
        newElements[len] = e;
        // 设置新数组为当前的集合数组
        setArray(newElements);
        return true;
    } finally {
        // 释放锁
        lock.unlock();
    }
}

**public E get(int index) **

  • 获取指定索引下的元素,如果元素越界则抛异常,否则返回元素
public E get(int index) {
    return get(getArray(), index);
}
private E get(Object[] a, int index) {
    return (E) a[index];
}

public E remove(int index)

  • 删除指定索引下的元素,并返回删除元素的值
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)
            // 如果numMoved等于0,说明删除的是尾部元素,
            // 则直接copy len-1的数组并赋值给当前集合
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            // 新建一个 size - 1的新数组
            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();
    }
}

public int size()

  • 返回数组元素的长度
public int size() {
    return getArray().length;
}
补充

五、总结

基本和ArrayList一样,只是没有扩容问题,每次添加元素都会直接创建一个新的数组,把老的赋值到新的数组中,然后再添加到新元素中,并进行赋值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值