大家好,我是侠客行0317,今天我们一起来看看CopyOnWriteArrayList底层实现。
在JDK1.8中,CopyOnWriteArrayList有如下类注释:
A thread-safe variant of java.util.ArrayList in which all mutative operations (add, set, and so on) are implemented by making a fresh copy of the underlying array. This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads.
java.util.ArrayList的线程安全变体,其中所有的变化操作(add、set等)都是通过创建底层数组的新副本来实现的。
这通常代价太大,但是当遍历操作的数量远远超过突变时,可能比其他方案更有效,并且当您不能或不想同步遍历,但需要排除并发线程之间的干扰时,这种方法很有用。
可见,对于修改操作,CopyOnWriteArrayList都会更新内部数组引用,从而实现对它的修改和访问,能够并行。
1 数据结构
主要属性如下。和ArrayList相比,CopyOnWriteArrayList的底层实现也是Object[],区别在于:
- CopyOnWriteArrayList初始容量为0,而ArrayList默认为10;
- 由于修改操作都会更新array为新对象,CopyOnWriteArrayList没有扩容机制;
- CopyOnWriteArrayList没有size属性,因为元素数量始终等于数组长度;而ArrayList有size属性。
// 修改操作加锁
final transient ReentrantLock lock = new ReentrantLock();
// 底层数组,应当只通过getArray/setArray访问
private transient volatile Object[] array;
2 构建实例
也可用
Collection实例,或数组来构建。
public CopyOnWriteArrayList(Collection<? extends E> c);
public CopyOnWriteArrayList(E[] toCopyIn);
3 主要方法
3.1 get方法
get(int index)直接访问数组索引。
3.2 add方法
会将当前array拷贝到长度为length + 1的新数组;更新array属性。
3.3 set方法
3.4 remove方法
移除列表中指定位置的元素,将所有后续元素向左移动,并返回被删除的元素。
3.5 clear方法
3.6 iterator
迭代器持有快照,在遍历时不会被修改操作所影响。自迭代器创建之后,迭代器将不会反映对列表的任何更改。
4 总结
4.1 特性
- 相比ArrayList,CopyOnWriteArrayList是线程安全的;
- 在读多写少的场景下,可使用CopyOnWriteArrayList来实现读写分离;
- 在写频繁的场景下,数组拷贝变多,内存占用变多,性能将有所下降;
- 对CopyOnWriteArrayList的修改操作,都会使用
ReentrantLock加锁,防止并发更新;但是读取时不加锁。 - CopyOnWrite机制,只能保证数据的最终一致性,不能实现实时一致性。
4.2 不用普通for遍历
此外,遍历CopyOnWriteArrayList时应使用迭代器,或forEach();如果使用普通for循环,很难避免ArrayIndexOutOfBoundsException。如下实例:
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class TestCopyOnWriteList {
public static final Random RANDOM = new Random();
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Integer> copy = Stream.generate(() -> RANDOM.nextInt(64))
.limit(10)
.collect(Collectors.toCollection(CopyOnWriteArrayList::new));
// 并发修改
ExecutorService threadPool = Executors.newFixedThreadPool(3);
threadPool.submit(() -> copy.remove(9));
threadPool.submit(() -> copy.remove(8));
threadPool.submit(() -> copy.remove(7));
// 不要用普通for遍历CopyOnWriteArrayList
for (int i = copy.size() - 1; i > 0; i++) {
// 模拟耗时
TimeUnit.MICROSECONDS.sleep(10);
System.out.println(copy.get(i));
}
threadPool.shutdown();
}
}
4.3 扩展-CopyOnWriteArraySet
从JDK1.5起,提供了CopyOnWriteArraySet类,它内部CopyOnWriteArrayList进行所有操作。
不同之处是它的
add(e)方法,为了避免重复元素,调用了addIfAbsent(e)。