批量删除元素的流程,与上面类似:
- 先使用ReentrantLock加锁,保证线程安全 。
- 再创建一个新数组,长度暂时使用原数组的长度,因为不知道要删除多少个元素 。
- 然后遍历原数组 , 把需要保留的元素放到新数组中 。
- 释放掉新数组中空白位置,再使用新数组替换掉原数组 。
- 最后释放锁
removeAll()
方法,因为 removeAll()
方法只涉及一次数组拷贝,性能比单个删除元素更好 。并发修改问题【CopyOnWriteArrayList底层是怎么保证线程安全的?】当遍历CopyOnWriteArrayList的过程中 , 同时增删CopyOnWriteArrayList中的元素,会发生什么情况?测试一下:
import java.util.List;import java.util.concurrent.CopyOnWriteArrayList;public class Test { public static void main(String[] args) { List<Integer> list = new CopyOnWriteArrayList<>(); list.add(1); list.add(2); list.add(2); list.add(3); // 遍历ArrayList for (Integer key : list) { // 判断如果元素等于2,则删除 if (key.equals(2)) { list.remove(key); } } System.out.println(list); }}
输出结果:[1, 3]
不但没有抛出异常,还把CopyOnWriteArrayList中重复的元素也都删除了 。原因是CopyOnWriteArrayList重新实现迭代器 , 拷贝了一份原数组的快照,在快照数组上进行遍历 。这样做的优点是其他线程对数组的并发修改,不影响对快照数组的遍历,但是遍历过程中无法感知其他线程对数组修改,有得必有失 。下面是迭代器的源码实现:static final class COWIterator<E> implements ListIterator<E> { /** * 原数组的快照 */ private final Object[] snapshot; /** * 迭代游标 */ private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } // 迭代下个元素 public E next() { if (!hasNext()) throw new NoSuchElementException(); return (E)snapshot[cursor++]; }}
总结现在可以回答文章开头提出的问题了吧:- CopyOnWriteArrayList初始容量是多少?
- CopyOnWriteArrayList是怎么进行扩容的?
- 加锁
- 创建一个新数组,长度原数组长度+1,并把原数组元素拷贝到新数组里面 。
- 释放锁
- CopyOnWriteArrayList是怎么保证线程安全的?
- 使用ReentrantLock加锁 , 保证操作过程中线程安全 。
- 使用volatile关键字修饰数组,保证当前线程对数组对象重新赋值后 , 其他线程可以及时感知到 。
推荐阅读
- 姜黎黎真是美了一辈子,69岁穿衣极简仍优雅高级,到老都没大妈感
- 睡觉多梦怎么办
- 跳绳怎么练能跳更快?
- 皮肤最好的状态是什么?
- 手机电脑的芯片主要是由什么物质组成
- 双胞胎名字是一对成语 双胞胎女儿洋气名字
- 忽见陌头杨柳色下一句,悔教夫婿觅封侯的完整诗句是什么
- 怎么用QQ开通微信,为什么用QQ账号还是登录不上微信
- 章子怡还是朱丹?这次周一围用行动说明了一切
- 久未露面的赵薇现身机场,手臂伤疤明显疑被家暴,47岁活像是个60岁大妈