一、把我能想到的写下来:

    并发包中只有一个 List:CopyOnWriteArrayList,写时复制。对于数组的 增删改,都是通过先获取锁 ReetrantLock ,通过 ReetrantLock 的lock() 获取锁,保证只有一个线程对数组进行修改,然后 unlock() 方法将锁释放,该类中有个 object 类型的数组 array 。

(补:
    CopyOnWriteArrayList 是个线程安全的 ArraytList,对其进行的操作 都是在底层的一个复制的数组 (快照:关于指定数据集合的一个完全可用拷贝 )。

)

    初始化:如果是无参构造,就会创建一个 长度为0 的array 。

    add() 方法:先获取独占锁,然后将原数组利用 Aaarys.copyOf() 进行拷贝,使引用 newArray 指向拷贝的数组,并在 newArray 中添加元素,然后还会用一次 Array.sort() ,拷贝新数组,将 array 引用指向拷贝后的新数组。

(改:
    只用了一次 Arrays.copyOf()。
    先获取独占锁,然后将原数组利用 Aaarys.copyOf() 进行拷贝,使引用 newElements指向拷贝的数组,这个 新数组的大小是原数组的大小增加1 ,然后把新增元素添加到新数组,再执行 setArray(newElements) ,使用新数组替换旧数组,setArray() 的源码如下:

final void setArray(Object[] a) {
        array = a;}

    需要注意的是:添加元素时,首先复制了一个快照,然后在快照上进行添加,而不是直接在原来数组上添加。
)

    remove()方法:先将原数组利用 Aaarys.sort() 进行拷贝,使引用 newArray 指向拷贝的数组,并在 newArray 中删除元素,然后分两步,先把 index 之前的拷贝一次,再将 index之后的拷贝一次??? 将 array 引用指向新数组。

(改:
    rremove 方法并没有先调用 Arrays.copyOf 拷贝原数组到新数组,该方法先获取 array, 赋给 elements ,然后判断要删除的元素是不是最后一个元素:如果是 ,调用 Arrays.copy(elements,len-1))替换原数;如果不是,分两次复制删除后剩余的元素到新数组 newElements ,调用的是 System.arraycopy() 方法,最后用 newElements 替换 array 。
     remove 方法不像 add、set 方法,一上来就把原数组拷贝到 array 中,这是需要注意的。

    get()方法,这个方法是没有用到 ReetrantLock 的,所以不保证同步,分两步,先获取 array ,再通过下标 index 取得相应元素。

    如下所示,当线程A 先获取到了 array {1,2,3},然后线程 B 拿到CPU,执行 remove ,删除掉1 ,删除的过程是:先拿到 ReetrantLock 确保只有这个线程会删除元素,然后拷贝到 newArray,删除 “1” 后再将 array 指向 {2,3},而线程 A 仍是指向的 {1,2,3}。

java 并发hashset java 并发list_增删改


(补:
    因此 线程A get(0) 取的元素仍是 1, 虽然 “1” 在线程 B 中已经被删除了。
    这就是 写时复制策略产生的 弱一致性 问题。)

    replace()方法:先取得 ReetrantLock 保证只有一个线程修改,然后取得要替换的元素 oldValue ,与替换值比较,不同则在新数组中替换掉,再赋给 array,如果相同,根据 volatile 原子性原则,需要更新,调用 setValue(object[ ] elempments)方法,体现原子性原则。该方法的实现是 :array=elempments;

(改:修改指定元素的方法是 set(int index,E element)))。

     迭代器的弱一致性:获取迭代器后,对数组的增、删、改操作对迭代器不可见。 因为增删改都是“写时复制”,如果有线程先获取值,然后再有线程更新值,是在拷贝的那个新数组中更新的 ,而后赋给 array ,而读取值的线程读到的还是旧的 array ,所以看不到更新。
    

二、附源码分析

1.add方法
public boolean add(E e) {

//先获取独占锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {

//获取 array
            Object[] elements = getArray();
            int len = elements.length;

//新数组的长度是原数组的长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);

//将添加的元素加到新数组的末尾 (所以 ArrayList 就能保证按添加的顺序存放,有些算法题里会遇到。)
            newElements[len] = e;

//用新数组替换原数组
            setArray(newElements);
            return true;
        } finally {

//释放锁
            lock.unlock();
        }
    }

    

2.获取指定位置元素
public E get(int index) {
        return get(getArray(), index);
    }

(1)先获取 array 数组

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

(2)通过下标访问指定位置元素

private E get(Object[] a, int index) {
        return (E) a[index];
    }

    

3.修改指定元素
public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();

//先通过 get 方法获取指定位置的元素
            E oldValue = get(elements, index);

//如果指定元素与新值不一致
            if (oldValue != element) {
                int len = elements.length;
                
                //新建数组
                Object[] newElements = Arrays.copyOf(elements, len);
                
                //并修改值
                newElements[index] = element;
                
                //用新数组替换原数组
                setArray(newElements);
            } else {

//如果指定位置元素和新值一样,体现以西 volatile 语义

          // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
final void setArray(Object[] a) {
    array = a;
}
4.删除元素
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];
 System.arraycopy(elements, 0, newElements, 0, index);
 System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);

//使用新数组替换原数组
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }
5.弱一致性的迭代器
public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }

COWIterator 的构造方法:

static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */

// array 的快照 snapshot
        private final Object[] snapshot;
 /** Index of element to be returned by subsequent call to next.  */

//数组下标
        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++];
        }

     为什么说 snapshot 是 list 的快照呢? 如果在线程使用返回的迭代器遍历元素的过程中,其他线程没有对 list 进行增删改,那么 spotshot 本身就是 list 的 array ,就是引用关系;但是 如果 其他线程对 list 进行了增删改,那么 snapshot 是 list 的快照了,因为 增删改 后 list 里面的数组被新数组替换了,这时,老数组被 spotshot 引用。 这也说明,获取迭代器后,使用该迭代器元素时,其他

三、总结

     CopyOnWriteArrayList 使用写时复制的策略来保证 list 的一致性,在 增删改 过程中都使用了 独占锁 ,来保证某个时刻只有一个线程能对 lsit 数组进行修改。另外 ,CopyOnWriteArrayList 提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对 list 的修改是不可见的,迭代器遍历的数组是一个快照。