COW

如果有多个调用者请求相同资源,他们会获得相同的指针指向同一个资源,知道某个调用者试图修改资源内容,系统才会真正复制一份专用副本给调用者,而其他调用者所见到的最初资源不变.

  • 优点:如果不修改,就不会有副本建立
  • 缺点:
    • 内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行add()、set()、remove()的话,那是比较耗费内存的。
      • 因为我们知道每次add()、set()、remove()这些增删改操作都要复制一个数组出来。
    • 数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性
      • 从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用setArray()了)。但是线程A迭代出来的是原有的数据。

CopyOnWriteArrayList

  • 线程安全
  • 底层通过复制数组实现
  • 遍历时候不会抛出ConcurrentModificationException异常
  • 元素可以为null

基本结构

  1. /** 可重入锁对象 */
  2. final transient ReentrantLock lock = new ReentrantLock();
  3. /** CopyOnWriteArrayList底层由数组实现,volatile修饰 */
  4. private transient volatile Object[] array;
  5. /**
  6. * 得到数组
  7. */
  8. final Object[] getArray() {
  9. return array;
  10. }
  11. /**
  12. * 设置数组
  13. */
  14. final void setArray(Object[] a) {
  15. array = a;
  16. }
  17. /**
  18. * 初始化CopyOnWriteArrayList相当于初始化数组
  19. */
  20. public CopyOnWriteArrayList() {
  21. setArray(new Object[0]);
  22. }

CopyOnWriteArrayList底层就是数组,加锁就交由ReentrantLock来完成。

add

  1. public boolean add(E e) {
  2. // 加锁
  3. final ReentrantLock lock = this.lock;
  4. lock.lock();
  5. try {
  6. // 得到原数组的长度和元素
  7. Object[] elements = getArray();
  8. int len = elements.length;
  9. // 复制出一个新数组
  10. Object[] newElements = Arrays.copyOf(elements, len + 1);
  11. // 添加时,将新元素添加到新数组中
  12. newElements[len] = e;
  13. // 将volatile Object[] array 的指向替换成新数组
  14. setArray(newElements);
  15. return true;
  16. } finally {
  17. lock.unlock();
  18. }
  19. }
  20. public int size() {
  21. // 直接得到array数组的长度
  22. return getArray().length;
  23. }
  24. public E get(int index) {
  25. return get(getArray(), index);
  26. }
  27. final Object[] getArray() {
  28. return array;
  29. }
  1. 上锁
  2. 复制一个新的数字
  3. 拷贝
  4. 在末尾加上被添加的元素
  5. 使元素指向新数组
  6. 解锁

set

  1. public E set(int index, E element) {
  2. final ReentrantLock lock = this.lock;
  3. lock.lock();
  4. try {
  5. // 得到原数组的旧值
  6. Object[] elements = getArray();
  7. E oldValue = get(elements, index);
  8. // 判断新值和旧值是否相等
  9. if (oldValue != element) {
  10. // 复制新数组,新值在新数组中完成
  11. int len = elements.length;
  12. Object[] newElements = Arrays.copyOf(elements, len);
  13. newElements[index] = element;
  14. // 将array引用指向新数组
  15. setArray(newElements);
  16. } else {
  17. // Not quite a no-op; enssures volatile write semantics
  18. setArray(elements);
  19. }
  20. return oldValue;
  21. } finally {
  22. lock.unlock();
  23. }
  24. }
  1. 加锁
  2. 获取index位置的值
  3. 如果值一样,不修改
  4. 值不一样,复制新数组,设置数组指向
  5. 解锁
  6. 返回

遍历

CopyOnWriteArrayList - 图1

  1. // 1. 返回的迭代器是COWIterator
  2. public Iterator<E> iterator() {
  3. return new COWIterator<E>(getArray(), 0);
  4. }
  5. // 2. 迭代器的成员属性
  6. private final Object[] snapshot;
  7. private int cursor;
  8. // 3. 迭代器的构造方法
  9. private COWIterator(Object[] elements, int initialCursor) {
  10. cursor = initialCursor;
  11. snapshot = elements;
  12. }
  13. // 4. 迭代器的方法...
  14. public E next() {
  15. if (! hasNext())
  16. throw new NoSuchElementException();
  17. return (E) snapshot[cursor++];
  18. }
  19. //.... 可以发现的是,迭代器所有的操作都基于snapshot数组,而snapshot是传递进来的array数组

可以发现的是,迭代器所有的操作都基于snapshot数组,而snapshot是传递进来的array数组
**