zoukankan      html  css  js  c++  java
  • Java集合003 --- CopyOnWriteArrayList

    前言

    CopyOnWriteArrayList实际上是ArrayList的线程安全版,内部实现也是数组,CopyOnWriteArrayList实现了RandomAccess、Cloneable、List、Serializable接口;

    属性

    // 可重入锁, 可重入的概念是: 当某个线程获取到锁之后, 该线程还能获取到该锁
    // 不自动序列化
    final transient ReentrantLock lock = new ReentrantLock();
    
    // 数组, 不自动序列化、修改对其他线程可见并且禁止指令重排
    private transient volatile Object[] array;

    可以看到,CopyOnWriteArrayList与ArrayList的区别是:CopyOnWriteArrayList无数据实际个数size,这是为啥呢?

    这是因为CopyOnWriteArrayList在写元素时,使用新的数组,然后赋值,因此数组的长度既是元素的个数

    构造方法

    final void setArray(Object[] a) {
        array = a;
    }
    
    public CopyOnWriteArrayList() {
        setArray(new Object[0]); // 创建容量为0的数组
    }
    
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            // 类型为CopyOnWriteArrayList, 强转然后获取数组
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            // 和ArrayList类似, 存在元素类型不为Object的场景
            elements = c.toArray();
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }
    
    public CopyOnWriteArrayList(E[] toCopyIn) {
        // 直接使用数组
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

    添加单个元素

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock(); // 加锁
        try {
            Object[] elements = getArray(); // 获取当前数组
            int len = elements.length;
            // 创建长度为len+1的新数组, 并且前len个元素为老数组的元素
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e; // 在新数组的最后添加元素
            setArray(newElements); // 更新list数组
            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) // index合法性检查
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            // 判断是不是在最后添加元素
            // 看了这几个list源码, 都是这样处理的;
            // 我写代码的思路是:所有的场景复用同一个流程, 看到此处源码, 感受颇多
            if (numMoved == 0)
                // 创建长度为len+1的新数组, 并且前len个元素为老数组的元素
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                // 先将原数组0~index-1个元素拷贝到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                // 将原数组index~end个元素拷贝到新数组index+1~end
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element; // 将新数组的第index位置赋值
            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;
            // 获取指定索引元素, 如果元素不存在, 抛ArrayIndexOutOfBoundsException
            // 而不是IndexOutOfBoundsException异常
            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];
                // 拷贝原数组0~index-1元素到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                // 拷贝原数组index+1~len元素到新数组
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);// 更新数组
            }
            return oldValue;// 返回index处的取值
        } finally {
            lock.unlock();//释放锁
        }
    }

    获取指定索引的元素

    public E get(int index) {
        // 不校验index, 数组越界抛ArrayIndexOutOfBoundsException
        return get(getArray(), index);
    }
    
    @SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

    如果元素不存在则添加元素(返回值:元素存在返回false;元素不存在返回true)

    public boolean addIfAbsent(E e) {
        Object[] snapshot = getArray();
        // 存在返回false; 不存在添加元素
        return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);
    }
    
    // 由于原数组是作为入参传入到该方法的, 此处也没有获取锁, 因此存在原数组被修改的情况
    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) {
                // 原数组被修改
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    if (current[i] != snapshot[i] && eq(e, current[i]))
                        return false;
                if (indexOf(e, current, common, len) >= 0)
                        return false;
            }
            // 在最后添加元素
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    对于集合来说,IfAbsent、IfPresent是很常见的方法,我在理解这块的功能的方法是:Absent是缺席的意思,AddIfAbsent的意思就是如果元素不存在则添加元素

    序列化和反序列化

    和ArrayList相似,都是采用按需序列化的方法

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException {
    
        s.defaultWriteObject();
    
        Object[] elements = getArray();
        // Write out array length
        s.writeInt(elements.length);
    
        // Write out all elements in the proper order.
        for (Object element : elements)
            s.writeObject(element);
    }
    
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
    
        s.defaultReadObject();
    
        // bind to new lock
        resetLock();
    
        // Read in array length and allocate array
        int len = s.readInt();
        Object[] elements = new Object[len];
    
        // Read in all elements in the proper order.
        for (int i = 0; i < len; i++)
            elements[i] = s.readObject();
        setArray(elements);
    }

    总结

    1、CopyOnWriteArrayList内部由数组实现,线程安全

    2、CopyOnWriteArrayList在写的时候,创建新的数组,写的时候内存占用高

    3、CopyOnWriteArrayList适用读多写少的场景

    4、CopyOnWriteArrayList能保证一致性,但是不能保证实时一致性

  • 相关阅读:
    一个很好的命令行分享网站
    Docker inside Docker 基于 Alpine Linux
    CentOS 下运行Docker 内执行 docker build 命令的简单方法
    CentOS 安装 Harbor的简单过程(仅使用http 未使用https)
    [财务会计] 表外科目
    jira 插件介绍地址
    Linux 下安装nginx的总结 (之前写的有问题))
    Jira 的 数据库备份恢复 简单过程
    Jira 7.2.4简单安装过程
    Tomcat绑定具体IP
  • 原文地址:https://www.cnblogs.com/sniffs/p/12926472.html
Copyright © 2011-2022 走看看