COW
如果有多个调用者请求相同资源,他们会获得相同的指针指向同一个资源,知道某个调用者试图修改资源内容,系统才会真正复制一份专用副本给调用者,而其他调用者所见到的最初资源不变.
- 优点:如果不修改,就不会有副本建立
- 缺点:
- 内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行
add()、set()、remove()
的话,那是比较耗费内存的。- 因为我们知道每次
add()、set()、remove()
这些增删改操作都要复制一个数组出来。
- 因为我们知道每次
- 数据一致性:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
- 从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用
setArray()
了)。但是线程A迭代出来的是原有的数据。
- 从上面的例子也可以看出来,比如线程A在迭代CopyOnWriteArrayList容器的数据。线程B在线程A迭代的间隙中将CopyOnWriteArrayList部分的数据修改了(已经调用
- 内存占用:如果CopyOnWriteArrayList经常要增删改里面的数据,经常要执行
CopyOnWriteArrayList
- 线程安全
- 底层通过复制数组实现
- 遍历时候不会抛出ConcurrentModificationException异常
- 元素可以为null
基本结构
/** 可重入锁对象 */
final transient ReentrantLock lock = new ReentrantLock();
/** CopyOnWriteArrayList底层由数组实现,volatile修饰 */
private transient volatile Object[] array;
/**
* 得到数组
*/
final Object[] getArray() {
return array;
}
/**
* 设置数组
*/
final void setArray(Object[] a) {
array = a;
}
/**
* 初始化CopyOnWriteArrayList相当于初始化数组
*/
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
CopyOnWriteArrayList底层就是数组,加锁就交由ReentrantLock来完成。
add
public boolean add(E e) {
// 加锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 得到原数组的长度和元素
Object[] elements = getArray();
int len = elements.length;
// 复制出一个新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 添加时,将新元素添加到新数组中
newElements[len] = e;
// 将volatile Object[] array 的指向替换成新数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
public int size() {
// 直接得到array数组的长度
return getArray().length;
}
public E get(int index) {
return get(getArray(), index);
}
final Object[] getArray() {
return array;
}
- 上锁
- 复制一个新的数字
- 拷贝
- 在末尾加上被添加的元素
- 使元素指向新数组
- 解锁
set
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 得到原数组的旧值
Object[] elements = getArray();
E oldValue = get(elements, index);
// 判断新值和旧值是否相等
if (oldValue != element) {
// 复制新数组,新值在新数组中完成
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
// 将array引用指向新数组
setArray(newElements);
} else {
// Not quite a no-op; enssures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
- 加锁
- 获取index位置的值
- 如果值一样,不修改
- 值不一样,复制新数组,设置数组指向
- 解锁
- 返回
遍历
// 1. 返回的迭代器是COWIterator
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
// 2. 迭代器的成员属性
private final Object[] snapshot;
private int cursor;
// 3. 迭代器的构造方法
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
// 4. 迭代器的方法...
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
//.... 可以发现的是,迭代器所有的操作都基于snapshot数组,而snapshot是传递进来的array数组
可以发现的是,迭代器所有的操作都基于snapshot数组,而snapshot是传递进来的array数组
**