CopyOnWriteArrayList

风格不统一 提交于 2019-11-29 21:56:47

CopyOnWriteArrayList

  1. 要意识到此为数据容器, 需考察其,,.
  2. 要留意其时空效率.
  3. 要意识到是开放类, 向Doug Lea学习类的设计.
  4. 根据特点思考使用场景

概述

  • java.util.ArrayList一个线程安全的变种, 消除ConcurrentModificationException.
    • 安全: 所有的变化操作都通过底层复制一个新的数组实现.
    • 思考: 相比ArrayList的空间,时间占用? 单线程情况和并发情况下.
    • 思考: System.arraycopy为什么快?
  • 一般来说代价较高,但是:
    • 遍历操作远超过数据改动操作的情况下更加有效
    • 遍历时不想或不可以使用synchronize消除并发干扰情况下更加有用
  • 迭代器不支持add,set,remove
  • 允许null值
  • 内存一致性:
    • 和其他并发容器一样, 增加数据操作所在的线程先于访问或移除元素操作所在的其他线程

实现

:

/**
* 同步锁. 忽略序列化.
*/
final transient Object lock = new Object();

/**
* 添加指定元素到列表尾端
*/
public boolean add(E e) {
    // 增的时候,需要加锁.
    synchronized (lock) {
        Object[] elements = getArray();
        int len = elements.length();
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    }
}

读的时候不需要加锁,如果读取时,别的线程在添加数据,读到还是旧容器中的数据.

:

    public E remove(int index) {
        synchronized (lock) {
            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;
        }
    }

:

    public E set(int index, E element) {
        synchronized (lock) {
            Object[] elements = getArray();
            E oldValue = get(elements, index);

            if (oldValue != element) {
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        }
    }

:

public synchronized E get(int index) {
    if (index >= elementCount) {
        throw new ArrayIndexOutOfBoundsException(index);
    }
    return elementData(index);
}

COW

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

注意事项

  1. 减少扩容的开销. 可以根据实际需要初始化大小,避免写时扩容的开销.
  2. 使用批量添加,减少添加次数.
  3. 内存占用,因为COW的写时复制机制, 在写的时候,内存里会同时驻扎两个内存对象. 如果这些对象占用内存较大,有可能造成频繁的GC.
  4. 数据一致性问题: 只能保证数据的最终一致性,不能保证实时一致性.
  5. 和Vector相比: 并发安全且性能好. 因为Vector各个方法都是有同步锁的,性能会大大下降.

规范

  1. Effective Java 13: 使类和成员的可访问性最小化
private transient volatile Object[] elements;
final Object[] getArray() {
    return elements;
}

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

get/set方法都是默认访问权限,平时我们都是直接访问elements, Doug Lea 通过get/set 访问.

  1. Effective Java 14: 在公有类中使用访问方法而非公有域
public int size() {
        return getArray().length;
    }
    
public boolean isEmpty() {
        return size() == 0;
    } 
  1. null值在index时的处理, 同理lastIndexOf的处理
    private static int indexOf(Object o, Object[] elements,int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i]))
                    return i;
        }
        return -1;
    }
  1. 如何抛出角标越界异常
static String outOfBounds(int index, int size) {
    return "Index: " + index + ", Size: " + size;
}
// 调用
throw new IndexOutOfBoundsException(outOfBounds(index, len));
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!