zoukankan      html  css  js  c++  java
  • CopyOnWriteArrayList

    参考: 死磕 java集合 by 彤哥读源码

    1. 概述

    1.1 简介

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

    1.2 继承体系

    主要是继承了List接口,提供一系列根据索引操作数组的方法

    2. 源码解析

    2.1 属性字段

    // 写时的加锁, 读不加锁
    final transient Object lock = new Object();
    
    // 存储元素的数组
    private transient volatile Object[] array;
    
    // 所有对array的操作都是通过getArray()和setArray()
    final Object[] getArray() {
        return array;
    }
    
    
    final void setArray(Object[] a) {
        array = a;
    }

    2.2 构造函数

    // 默认构造函数
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    
    // 集合构造
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] es;
        if (c.getClass() == CopyOnWriteArrayList.class)
            // 因为是读写分离的, 不必担心此集合被其它集合修改问题
            // 因此直接指向其数组
            es = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            es = c.toArray();
            // 其它类型集合不能保证, 因此需要复制
            if (es.getClass() != Object[].class)
                es = Arrays.copyOf(es, es.length, Object[].class);
        }
        setArray(es);
    }
    
    
    // 直接传递数组也需要复制
    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

    2.3 常规操作

    // 尾插
    public boolean add(E e) {
        // 写时加锁, 保证写的线程安全
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            // 数组长度和元素数量是一致的
            // 添加就是重新创建一个数组
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }
    // 插入指定位置
    public void add(int index, E element) {
        // 写时加锁
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException(outOfBounds(index, len));
            Object[] newElements;
            int numMoved = len - index;
            // 尾插
            if (numMoved == 0)
                newElements = Arrays.copyOf(es, len + 1);
            // 非尾插
            else {
                // 新建容量+1的数组, 复制原数组元素
                newElements = new Object[len + 1];
                System.arraycopy(es, 0, newElements, 0, index);
                System.arraycopy(es, index, newElements, index + 1,
                                 numMoved);
            }
            // 添加元素
            newElements[index] = element;
            //
            setArray(newElements);
        }
    }
    // 移除元素
    public E remove(int index) {
        // 加锁
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            E oldValue = elementAt(es, index);
            int numMoved = len - index - 1;
            Object[] newElements;
            // 尾部移除, 直接缩容
            if (numMoved == 0)
                newElements = Arrays.copyOf(es, len - 1);
            else {
                // 新建容量-1数组, 并复制
                newElements = new Object[len - 1];
                System.arraycopy(es, 0, newElements, 0, index);
                System.arraycopy(es, index + 1, newElements, index,
                                 numMoved);
            }
            setArray(newElements);
            return oldValue;
        }
    }
    // 这个是线程安全的?
    public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOfRange(o, snapshot, 0, snapshot.length);
        // 调用remove之前数组实际上已经被改变了呢?
        return index >= 0 && remove(o, snapshot, index);
    }
    // get不用加锁, 即读不加锁
    public E get(int index) {
        return elementAt(getArray(), index);
    }
    
    // 只要有改变元素内容的都要加锁
    public E set(int index, E element) {
        // 加锁
        synchronized (lock) {
            Object[] es = getArray();
            E oldValue = elementAt(es, index);
    
            if (oldValue != element) {
                es = es.clone();
                es[index] = element;
            }
            // 没有数组长度的增加与减少, 设置的数组还是原数组
            setArray(es);
            return oldValue;
        }
    }
     1 public boolean remove(Object o) {
     2     Object[] snapshot = getArray();
     3     int index = indexOfRange(o, snapshot, 0, snapshot.length);
     4     // 有元素才会进行处理, 不过因为上面没有加锁, 在此时集合的array可能已经改变了
     5     return index >= 0 && remove(o, snapshot, index);
     6 }
     7 
     8 // ??, 反正是删除
     9 private boolean remove(Object o, Object[] snapshot, int index) {
    10     // 加锁
    11     synchronized (lock) {
    12         // 重新获取array
    13         Object[] current = getArray();
    14         int len = current.length;
    15         // array已经被改变, 即快照无效
    16         if (snapshot != current) findIndex: {
    17             // min(index, len)之间查询, 因为可能缩容了
    18             int prefix = Math.min(index, len);
    19             for (int i = 0; i < prefix; i++) {
    20                 // 
    21                 if (current[i] != snapshot[i]
    22                     && Objects.equals(o, current[i])) {
    23                     // 提前定位到数据位置了, 而且这个数据和快照的数据还不是同一个对象
    24                     index = i;
    25                     break findIndex;
    26                 }
    27             }
    28             // 没有数据
    29             if (index >= len)
    30                 return false;
    31             // 
    32             if (current[index] == o)
    33                 break findIndex;
    34             // 
    35             index = indexOfRange(o, current, index, len);
    36             if (index < 0)
    37                 return false;
    38         }
    39         // 常规删除
    40         Object[] newElements = new Object[len - 1];
    41         System.arraycopy(current, 0, newElements, 0, index);
    42         System.arraycopy(current, index + 1,
    43                          newElements, index,
    44                          len - index - 1);
    45         setArray(newElements);
    46         return true;
    47     }
    48 }
    1 public int size() {
    2     return getArray().length;
    3 }

    3. 总结

    • 使用synchronized加锁,保证线程安全
    • 写操作涉及到容量改变的都会加锁,并新建数组并拷贝数据,并在新数组中增删
    • 写操作不涉及到容量如set只是加锁
    • 使用读写分离的思想,读不加锁,写加锁
    • 写操作基本都是要加锁、要新建数组、要拷贝数据的,因此效率很低,适合于读多写极少的情况
    • 只保证最终一致性,不保证实时一致性(读时读到的可能是旧数组,而新数组还在处理中)
    • 没有size属性,因为数组容量和数组长度是相同的
  • 相关阅读:
    Oracle体系结构详细图解
    了解Oracle体系结构(超详细)
    Oracle 12C DataGuard部署以及维护
    RAC上,对于buffer cache的全局锁,称为PCM锁,当然,对于enq,lc的锁,称为non-PCM锁
    oracle锁与死锁概念,阻塞产生的原因以及解决方案
    Oracle 多粒度锁机制介绍
    Master Note For Oracle Database Upgrades and Migrations (Doc ID 1152016.1)
    Oracle 12c和18c中的MGMTDB(下)
    AIX系统性能管理之Oracle案例分析
    Web安全从入门到“放弃”之暴力破解
  • 原文地址:https://www.cnblogs.com/chenxingyang/p/12731002.html
Copyright © 2011-2022 走看看