写入时复制是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。
集合数据主要就两个操作读、写;读是读取内容,写是改变内容;读的时候读取存储资源,写的时候,复制一份修改后再重置原有资源;这样就具有以下特色:
因此如果要做到线程安全,就必须再写时进行加锁
而CopyOnWriteArrayList集合就是这种写时复制+写时加锁来实现的;CopyOnWriteArraySet内部代理了CopyOnWriteArrayList,进而实现不重复数据的集合;下面就来介绍下CopyOnWriteArrayList
list集合,内部采用数组来存储;写时复制,并加锁;直接读数据;
final transient Object lock = new Object(); private transient volatile Object[] array; 复制代码
lock是对synchronized锁标志,array是资源数组,使用volatile关键字,写操作会再任何线程下次操作资源时可见
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 void add(int index, E element) { synchronized (lock) { Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0) throw new IndexOutOfBoundsException(outOfBounds(index, len)); Object[] newElements; int numMoved = len - index; if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } } 复制代码
特定索引增加数据,可以任意位置增加数据;
保证数据不重复,增加数据
public boolean addIfAbsent(E e) { Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } private boolean addIfAbsent(E e, Object[] snapshot) { synchronized (lock) { Object[] current = getArray(); int len = current.length; if (snapshot != current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && Objects.equals(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; } } 复制代码
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 { setArray(elements); } return oldValue; } } 复制代码
改变指定位置数据:
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; } } 复制代码
加锁,进行copy数据,copy数据时,不包括要删除的数据,把copy数据重置回去
public boolean remove(Object o) { Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); return (index < 0) ? false : remove(o, snapshot, index); } private boolean remove(Object o, Object[] snapshot, int index) { synchronized (lock) { Object[] current = getArray(); int len = current.length; if (snapshot != current) findIndex: { int prefix = Math.min(index, len); for (int i = 0; i < prefix; i++) { if (current[i] != snapshot[i] && Objects.equals(o, current[i])) { index = i; break findIndex; } } if (index >= len) return false; if (current[index] == o) break findIndex; index = indexOf(o, current, index, len); if (index < 0) return false; } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } } 复制代码
public void clear() { synchronized (lock) { setArray(new Object[0]); } } 复制代码
加锁,置为空数组
很简单的逻辑,获取数据索引位置
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; } 复制代码
还有一些其它方法,比如获取位置索引,集合大小等
就不介绍了,它内部使用了CopyOnWriteArraySet来代理功能;并且减少了方法