并发List中的CopyOnWriteArrayList

580

CopyOnWriteArrayList介绍

CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是模仿了写时复制策略

源码分析

初始化:内部创建了一个大小为0的Object数组作为array的初始值

==有参构造函数==

添加元素

  • 调用add方法的线程会首先执行代码去获取独占锁,如果多个线程都调用add方法则只有一个线程会获取到该锁,其他线程会被阻塞挂起直到锁被释放。
  • 所以一个线程获取到锁后,就保证了在该线程添加元素的过程中其他线程不会对array进行修改。
  • 线程获取锁后执行代码获取array,然后执行代码复制array到一个新数组(从这里可以知道新数组的大小是原来数组大小增加1,所以CopyOnWriteArrayList是无界list),并把新增的元素添加到新数组。
  • 然后执行代码使用新数组替换原数组,并在返回前释放锁。由于加了锁,所以整个add过程是个原子性操作。需要注意的是
  • 在添加元素时,首先复制了一个快照,然后在快照上进行添加而不是直接在原来数组上进行

修改指定元素E set(int index,E element)

public E set(int index, E element) {
            final ReentrantLock lock = l.lock;
            lock.lock();
            try {
                rangeCheck(index);
                checkForComodification();
                E x = l.set(index+offset, element);
                expectedArray = l.getArray();
                return x;
            } finally {
                lock.unlock();
            }
     }
  • 首先获取了独占锁,从而阻止其他线程对array数组进行修改,然后获取当前数组,并调用get方法获取指定位置的元素
  • 如果指定位置的元素值与新值不一致则创建新数组并复制元素,然后在新数组上修改指定位置的元素值并设置新数组到array
  • 如果指定位置的元素值与新值一样,则为了保证volatile语义,还是需要重新设置array,虽然array的内容并没有改变。

删除元素

  • 首先获取独占锁以保证删除数据期间其他线程不能对array进行修改,然后获取数组中要被删除的元素
  • 并把剩余的元素复制到新数组,之后使用新数组替换原来的数组,最后在返回前释放锁。

弱一致性的迭代器

public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("hello");
        list.add("alibaba");

        Iterator<String> itr = list.iterator();
        while(itr.hasNext()){
            System.out.println(itr.next());
        }
    }
  • 迭代器的hasNext方法用于判断列表中是否还有元素,next方法则具体返回元素。
public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }



    static final class COWIterator<E> implements ListIterator<E> {
        // 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;
        }

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

      ·················································
        }
    }
  • 如果在该线程使用返回的迭代器遍历元素的过程中,其他线程没有对list进行增删改,那么snapshot本身就是list的array
  • 因为它们是引用关系。但是如果在遍历期间其他线程对该list进行了增删改,那么snapshot就是快照了,- 因为增删改后list里面的数组被新数组替换了,这时候老数组被snapshot引用。这也说明获取迭代器后
  • 使用该迭代器元素时,其他线程对该list进行的增删改不可见,因为它们操作的是两个不同的数组,这就是弱一致性。

输出结果

hello
GG/MM
Welcome
to
xiaoff zone
  • main函数首先初始化了arrayList,然后在启动线程前获取到了arrayList迭代器。
  • 子线程threadOne启动后首先修改了arrayList的第一个元素的值,然后删除了arrayList中下标为23的元素。
  • 主线程在子线程执行完毕后使用获取的迭代器遍历数组元素,从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭代器弱一致性的体现。
  • 需要注意的是,获取迭代器的操作必须在子线程操作之前进行。