栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JAVA容器系列一:CopyOnWriteArrayList源码解读

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JAVA容器系列一:CopyOnWriteArrayList源码解读

一、CopyOnWriteArrayList介绍
①、CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
②、CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。
③、CopyonWriteArrayList 合适读多写少的场景,不过这类慎用 ,因为谁也没法保证CopyonWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。

构造函数:

private transient volatile Object[] array;//通过volatile保证可见性,但是其实也影响一点性能。因为每次都需要写回主内存之后,再从主内存强制加载到工作内存。

 public CopyOnWriteArrayList() {
 // 创建了一个大小为0的Object数据作为array的初始值
        setArray(new Object[0]);
    }

Add方法:

final transient ReentrantLock lock = new ReentrantLock();

public boolean add(E e) {
        //加独占锁(1)
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //获取array(2)
            Object[] elements = getArray();

            //拷贝array到新数组,添加元素到新数组(3)
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;

            //使用新数组替换添加前的数组(4)
            setArray(newElements);
            return true;
        } finally {
            //释放独占锁(5)
            lock.unlock();
        }
 }

Get方法:

public E get(int index) {
//获取到当前list里面的array数组,步骤1
    return get(getArray(), index);
} 
//当执行完步骤1后执行步骤2前,另外一个线程C进行了修改操作,比如remove操作(修改当前list的array为新数组),
则步骤2操作的是线程C删除元素前的一个快照数组(因为步骤1让array指向的是原来的数组),这就是写时拷贝策略带来弱一致性.
private E get(Object[] a, int index) {
//通过随机访问的下标方式访问指定位置的元素,步骤2。
    return (E) a[index];
}

Set方法

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

Remove方法

public E remove(int index) {
        //获取独占锁保证删除数组期间,其他线程不能对array进行修改
        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();
        }
    }
迭代器的弱一致性:
private static volatile CopyOnWriteArrayList arrayList = new CopyOnWriteArrayList<>();

    @SneakyThrows
    public static void main(String[] args) {
        arrayList.add("hello");
        arrayList.add("Java");
        arrayList.add("welcome");
        arrayList.add("to");
        arrayList.add("hangzhou");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //修改list中下标为1的元素为JavaSe
                arrayList.set(1, "JavaSe");
                //删除元素
                arrayList.remove(2);
                arrayList.remove(3);
            }
        });
        //保证在修改线程启动前获取迭代器
        Iterator itr = arrayList.iterator();
        //启动线程
        thread.start();
        //等在子线程执行完毕
        thread.join();

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

    }

结果如下:

CopyOnWriteArrayList中迭代器是弱一致性,所谓弱一致性是指返回迭代器后,其他线程对list的增删改对迭代器不可见,无感知的.下面就看看是如何做到的:

public Iterator iterator() {
    return new COWIterator(getArray(), 0);
}

static final class COWIterator implements ListIterator {
    //array的快照版本
    private final Object[] snapshot;

    //数组下标
    private int cursor;

    //构造函数
    private COWIterator(Object[] elements, int initialCursor) {
        cursor = initialCursor;
        snapshot = elements;
    }

    //是否遍历结束
    public boolean hasNext() {
        return cursor < snapshot.length;
    }

    //获取元素
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }}

二、CopyonWriteArrayList 有几个缺点:
1、由于写操作的时候,需要拷贝数组,会消耗内存,如果原数组的内容比较多的情况下,可能导致young gc或者full gc。

2、不能用于实时读的场景,像拷贝数组、新增元素都需要时间,所以调用一个set操作后,读取到数据可能还是旧的,虽然CopyonWriteArrayList 能做到最终一致性,但是还是没法满足实时性要求,如:;

public static void main(String[] args) {
        List list = new CopyOnWriteArrayList<>();
        for(int i = 0; i<10000; i++){
            list.add("string" + i);
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if (list.size() > 0) {
                        String content = list.get(list.size() - 1);
                    }else {
                        break;
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    if(list.size() <= 0){
                        break;
                    }
                    list.remove(0);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 9963
	at java.util.concurrent.CopyOnWriteArrayList.get(CopyOnWriteArrayList.java:388)
	at java.util.concurrent.CopyOnWriteArrayList.get(CopyOnWriteArrayList.java:397)
	at com.example.demo.controller.Test01Cont$1.run(Test01Cont.java:64)
	at java.lang.Thread.run(Thread.java:748)

从上可以看出CopyOnWriteArrayList并不是完全意义上的线程安全,如果涉及到remove操作,一定要谨慎处理。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/755670.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号