为了账号安全,请及时绑定邮箱和手机立即绑定

Java并发包中的CopyOnWriteArrayList

标签:
Java 源码

一、CopyOnWriteArrayList

在Java的并发包的并发List中只有CopyOnWriteArrayList。

CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略实现的。

通过查看CopyOnWriteArrayList的源码可以看出,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,其中含有一个ReentrantLock独占锁对象用来保证同时只有一个线程对array的修改。

/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();

/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;

CopyOnWriteArrayList会在内部创建一个大小为0的Object数组作为array的初始值,其源码如下:

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
}

CopyOnWriteArrayList的有参构造函数源码如下:

 //这个构造函数接收一个集合元素,它会将集合里面的元组复制到本list中
 public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            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);
 }

 

 //这个构造函数用于创建一个list,其内部元素是入参toCopyIn的副本。   
 public CopyOnWriteArrayList(E[] toCopyIn) {
         setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
 }

二、向CopyOnWriteArrayList中添加元素               

CopyOnWriteArrayList中用来添加元素的函数有add(E e), add(int index, E element), addIfAbsent(E e)和addAllAbsent(Collection<? extends E> e)等,其原理类似。

add(E w)函数的源码如下:

public boolean add(E e) {
        //获取独占锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取array
            Object[] elements = getArray();
            //获取数组长度
            int len = elements.length;
            //复制array到新数组,添加元素到新数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            //使用新数组替换添加前的数组
            setArray(newElements);
            return true;
        } finally {
            //释放独占锁
            lock.unlock();
        }
}

在多线程情况下,如果多个线程同时去调用CopyOnWriteArrayList的add(E e)方法时,只有一个线程会获取到锁,其他线程会被阻塞挂起直到独占锁被释放。

当线程获取到锁之后,就可以保证在该线程添加元素的过程中其他线程不会对array进行操作。

在执行代码Object[] newElements = Arrays.copyOf(elements, len + 1);复制array到一个新数组时,新数组的大小是原来数组大小加1,从这里我们可以知道CopyOnWriteArrayList是一个无界list。

add(E e)方法内部使用加锁机制,所以整个add过程是个原子性操作。值得注意的是,在添加元素时,首先复制了一个快照,然后在快照上进行添加,而不是在原来数组上进行的。


三、从CopyOnWriteArrayList中获取指定位置的元素     

要从CopyOnWriteArrayList中获取指定位置的元素,我们可以使用它的 E get(int index)来获取下标为index的元素,如果元素不存在则抛出IndexOutOfBoundsException异常。其源码如下:

//该方法是通过下标来获取元素
public E get(int index) {
    return get(getArray(), index);
}


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

    

//该方法是首先获取array数组,然后通过下标访问指定位置的元素
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
   return (E) a[index];
}

四、修改CopyOnWriteArrayList指定位置的元素     

要修改CopyOnWriteArrayList指定位置的元素,可以使用CopyOnWriteArrayList中的E set(int index, E element)方法来修改list指定元素的值,如果指定位置的元素不存在,则抛出IndexOutOfBoundsException异常。其源码如下:

//修改指定下标为index的元素的值为element
 public E set(int index, E element) {
        //获取独占锁,以保证其他线程对array数组进行修改
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取当前数组
            Object[] elements = getArray();
            //获取数组指定下标的旧值
            E oldValue = get(elements, index);
            //如果指定位置的元素值与新值不相等则创建新数组并复制元素
            if (oldValue != element) {
                //获取数组的长度
                int len = elements.length;
                //在新数组上修改指定位置的元素值并设置新数组到array
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                //如果指定位置的元素值与新值相等,为了保证volatile的内存语义,还需要重新设置array
                setArray(elements);
            }
            return oldValue;
        } finally {
            //释放锁
            lock.unlock();
        }
 }

  

五、删除CopyOnWriteArrayList中的元素           

要删除CopyOnWriteArrayList中的元素,可以使用E remove(int index)、boolean remove(Object o)和boolean remove(Object 0, Object[] snapshot, int index)等方法实现,它们的原理相似。

E remove(int index)方法的源码如下:

public E remove(int index) {
    //获取独占锁,以保证删除数据期间其他线程不能对array进行修改
    final ReentrantLock lock = this.lock;
    //加锁
    lock.lock();
    try {
        //获取当前数组
        Object[] elements = getArray();
        //获取数组长度
        int len = elements.length;
        //根据下标index获取数组指定元素
        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();
    }
}

  

六、遍历CopyOnWriteArrayList

System.out.println("for循环遍历列表:");
int len = arrayList.size();
for(int i = 0; i< len; i++ ){
    System.out.println(arrayList.get(i));
}
//使用迭代器遍历
System.out.println("迭代器遍历列表:");
Iterator<String> itr = arrayList.iterator();
while (itr.hasNext()){   //hasNext()方法用于判断列表中是否还有元素
    System.out.println(itr.next());   //next()方法则具体返回元素
}

示例代码1如下:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName: CopyOnWriteArrayListDemo
 * @Description:  CopyOnWriteArrayList用法
 * @Author: liuhefei
 * @Date: 2019/12/22
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class CopyOnWriteArrayListDemo {

    public static void main(String[] args) {
        CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
        //添加元素
        arrayList.add("北京");
        arrayList.add("上海");
        arrayList.add("深圳");
        arrayList.add("昆明");
        arrayList.add("天津");
        arrayList.add("银川");
        arrayList.add("兰州");
        arrayList.add("成都");
        System.out.println("arrayList = " + arrayList);

        //CopyOnWriteArrayList是一个无界list。
        //修改指定位置的元素
        arrayList.set(4, "广州");   //注意这里的下标index不能超出列表的长度,否则报错
        arrayList.set(5, "武汉");
        System.out.println("newArrayList = " + arrayList);

        //获取指定位置的元素
        String city = arrayList.get(4);
        System.out.println("city = " + city);

        //删除指定位置的元素
        String deleteCity = arrayList.remove(4);
        System.out.println("deleteCity = " + deleteCity);
        System.out.println("arrayList = " + arrayList);

        //遍历arrayList
        System.out.println("for循环遍历列表:");
        int len = arrayList.size();
        for(int i = 0; i< len; i++ ){
            System.out.println(arrayList.get(i));
        }

        //使用迭代器遍历
        System.out.println("迭代器遍历列表:");
        Iterator<String> itr = arrayList.iterator();
        while (itr.hasNext()){   //hasNext()方法用于判断列表中是否还有元素
            System.out.println(itr.next());   //next()方法则具体返回元素
        }

    }
}

示例代码2如下:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * @ClassName: CopyOnWriteArrayListDemo1
 * @Description: CopyOnWriteArrayList用法
 * 主线程在子线程执行完毕后使用获取的迭代器遍历数组元素,从运行的结果来看,在子线程中进行的操作一个都没有生效,这就是迭代器弱一致性的体现,
 * 需要注意的是:获取迭代器的操作必须在子线程操作之前进行
 * @Author: liuhefei
 * @Date: 2019/12/22
 * @blog: https://www.imooc.com/u/1323320/articles
 **/
public class CopyOnWriteArrayListDemo1 {

    private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        arrayList.add("北京");
        arrayList.add("上海");
        arrayList.add("广州");
        arrayList.add("深圳");
        arrayList.add("武汉");
        arrayList.add("成都");
        arrayList.add("郑州");
        arrayList.add("长沙");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //修改list中下标为4的元素为 昆明
                arrayList.set(4, "昆明");
                //删除元素
                arrayList.remove(5);
                arrayList.remove(6);
            }
        });

        //保证在修改线程启动前获取迭代器
        Iterator<String> itr = arrayList.iterator();

        //启动线程
        thread.start();

        //等待子线程执行完毕
        thread.join();

        //迭代元素
        while (itr.hasNext()){
            System.out.println(itr.next());
        }
    }
}


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
1.5万
获赞与收藏
8507

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消