zoukankan      html  css  js  c++  java
  • ArrayList和CopyOnWriteArrayList

    ArrayList和CopyOnWriteArrayList##

    参考:

    ArrayList###

    • 底层是数组,初始大小为10
    • 插入时会判断数组容量是否足够,不够的话会进行扩容
    • 所谓扩容就是新建一个新的数组,然后将老的数据里面的元素复制到新的数组里面
    • 移除元素的时候也涉及到数组中元素的移动,删除指定index位置的元素,然后将index+1至数组最后一个元素往前移动一个格

    源码:

    // 增
    public boolean add(E e) {
        //进行数组容量判断,不够就扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
    
    public void add(int index, E element) {
        //检查是否会越界
        rangeCheckForAdd(index);
        //进行数组容量判断,不够就扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //将index至数据最后一个元素整体往后移动一格,然后插入新的元素
        System.arraycopy(elementData, index, elementData, index + 1,
                        size - index);
        elementData[index] = element;
        size++;
    }
    
    
    // 删
    public E remove(int index) {
        //判断是否越界
        rangeCheck(index);
    
        modCount++;
        E oldValue = elementData(index);
    
        int numMoved = size - index - 1;
        //若该元素不是最后一个元素的话,将index+1至数组最后一个元素整体向前移动一格
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    
        return oldValue;
    }
    
    
    // 改
    public E set(int index, E element) {
        rangeCheck(index);
    
        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }
    
    // 查
    public E get(int index) {
        rangeCheck(index);
    
        return elementData(index);
    }
    
    E elementData(int index) {return (E) elementData[index]; }
    

    ArrayList的底层是数组,所以查询的时候直接根据索引可以很快找到对应的元素,改也是如此,找到index对应元素进行替换。而增加和删除就涉及到数组元素的移动,所以会比较慢。

    CopyOnWriteArrayList###

    • 实现了List接口
    • 内部持有一个ReentrantLock lock = new ReentrantLock();
    • 底层是用volatile transient声明的数组 array
    • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array

    volatile (挥发物、易变的):变量修饰符,只能用来修饰变量。volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变 化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

    transient (暂短的、临时的):修饰符,只能用来修饰字段。在对象序列化的过程中,标记为transient的变量不会被序列化。

    源码:

    // 增
    public boolean add(E e) {
    	final ReentrantLock lock = this.lock;
        //获得锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //复制一个新的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //插入新值
            newElements[len] = e;
            //将新的数组指向原来的引用
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }
    
       
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } 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];
                //将index+1至最后一个元素向前移动一格
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } 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();
        }
    }
    
    // 查
    //直接获取index对应的元素
    public E get(int index) {return get(getArray(), index);}
    private E get(Object[] a, int index) {return (E) a[index];}
    

    增删改都需要获得锁,并且锁只有一把,而读操作不需要获得锁,支持并发。

    CopyOnWrite的缺点####

    CopyOnWrite容器有很多优点,但是同时也存在两个问题,即内存占用问题和数据一致性问题。

    • 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象
    • 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

    CopyOnWriteArrayList为什么并发安全且性能比Vector好####

    Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

  • 相关阅读:
    网络通讯协议的基本要素
    java实现二维码的生成与解析
    SpringCloud应用间通信-RestTemplate与Feign
    SpringCloud服务注册与发现-Eureka、Nacos和Consul
    极光推送-java消息推送app
    Git的回滚和撤销操作
    SOFABoot学习
    记录一次生产环境下EleasticSearch故障(cpu打满)
    记录SQL优化
    利用二进制存储多种状态
  • 原文地址:https://www.cnblogs.com/cuiyf/p/11158579.html
Copyright © 2011-2022 走看看