大家在学习Java的过程中,或者工作中,始终都绕不开集合。在单线程环境下,ArrayList就可以满足要求。多线程时,我们可以使用CopyOnWriteArrayList来保证数据安全。下面我们一起来看看CopyOnWriteArrayList类中的一些值得学习的方法。

说明:代码部分,均基于JDK1.8

一、添加元素

写时复制思想

CopyOnWrite, 简称COW,顾名思义,就是写入的时候将当前集合复制一份副本出来,新写入的值添加到副本集合里,再将原集合的引用指向新的副本集合。基于这个原理,就可以不加锁实现并发读,因为当前集合并不会添加元素,不会造成冲突。同样的原理还应用在MySQL中创建快照的过程。

	/**
* 将指定的元素追加到此列表的末尾
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#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;
// 改变引用
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

二、删除元素

ArrayList 遍历过程中删除的问题

大家在学习Java期间,一定都有过使用forEach遍历ArrayList时删除元素都会得到一个java.util.ConcurrentModificationException的错误。这是因为在ArrayList的remove()方法中,有一个参数modCount 专门用来记录修改的次数,每删除一次就modCount++。在forEach遍历集合时,首先会记录final int expectedModCount = modCount,若是遍历过程中发现expectedModCount!=modCount,则会抛出错误。

下面来看看具体代码

    /**
* 删除元素
*/
public E remove(int index) {
// 检查下标是否越界
rangeCheck(index);
// 记录修改次数
modCount++;
// 待删除的元素
E oldValue = elementData(index);
// 待删除元素下标之后的数组长度
int numMoved = size - index - 1;
if (numMoved > 0)
// 删除元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work return oldValue;
} /**
* forEach 方法
*/
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
// 记录modCount
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
// 遍历时判断modCount
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
// 不相同,抛出异常
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}

所以使用ArrayList时,如果你有遍历删除某个元素的场景,我们可以使用迭代器来删除。

CopyOnWriteArrayList 的删除

先来看看CopyOnWriteArrayList的remove()方法的源码,整体逻辑与ArrayList的remove()方法一直,有区别的是没有记录修改次数,因为每次删除都是重新获取的当前数组。而forEach()方法在遍历时也是获取的当前数组,因此在使用forEach遍历时删除元素不会抛出异常。

	/**
* 删除元素
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
// 加锁
lock.lock();
try {
// 获取当前数组
Object[] elements = getArray();
// 记录数组的长度
int len = elements.length;
// 记录待删除元素
E oldValue = get(elements, index);
// 记录待删除元素后一个元素到尾节点的长度
int numMoved = len - index - 1;
// 为0代表待删除元素就在数组的末尾
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;
} finally {
lock.unlock();
}
} /**
* 遍历方法
*/
public void forEach(Consumer<? super E> action) {
if (action == null) throw new NullPointerException();
// 获取当前数组
Object[] elements = getArray();
int len = elements.length;
for (int i = 0; i < len; ++i) {
@SuppressWarnings("unchecked") E e = (E) elements[i];
action.accept(e);
}
}

值得注意的是,CopyOnWriteArrayList的迭代器实现里的remove()方法会直接抛出异常,因此在使用迭代器遍历元素时,不能删除元素。

总结

使用场景

基于原理,不难分析出CopyOnWriteArrayList适用于读多写少的并发环境

需要注意的点

因为每次添加元素都需要复制一份副本,所以最好是使用批量添加,减少复制副本的次数

缺点

一、内存占用问题。 因为 CopyOnWrite 的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,这一点会占用额外的内存空间。

二、数据一致性问题。 由于 CopyOnWrite 容器的修改是先修改副本,所以这次修改对于其他线程来说,并不是实时能看到的,只有在修改完之后才能体现出来。如果你希望写入的的数据马上能被其他线程看到,CopyOnWrite 容器是不适用的。

最新文章

  1. 一步步开发自己的博客 .NET版(10、前端对话框和消息框的实现)
  2. Spring的jdbcTemplate查询执行原生sql
  3. oracle删除users表空间
  4. oracle中的函数及其应用
  5. 删除root子目录,如何恢复子目录配置文件
  6. SQL Server被锁的表以及解锁
  7. ASP.NET MVC使用input标签上传文件
  8. 如何通过SecureCRT FTP上传下载文件
  9. 如何使用 Apache ab 以及 OneAPM 进行压力测试?
  10. CMS漏洞
  11. CBO学习----03--选择率(Selectivity)
  12. gradle多模块开发(转)
  13. ntfs mount fail after upgrade win10
  14. Kali Linux 工具使用中文说明书
  15. Jmeter读取Excel,BeanShell取样器调用rt.jar和jxl.jar
  16. sublime Text3 插件
  17. idea checkout 项目
  18. C++11 boost TR1 TR2曾经参考过的网址整理
  19. Loadrunner使用键盘快捷键
  20. Https 安全传输的原理

热门文章

  1. CDR中调和工具的使用方法
  2. Vegas转场功能的妙用,让片头转场更酷炫
  3. B 站今日黑白页是怎么实现的?
  4. SpringBoot 实现微信推送模板
  5. FDR校正
  6. 【Usaco 2009 Gold 】JZOJ2020年9月19日提高B组T2 电视游戏问题
  7. 20200322_【转载】关于C#中使用SQLite自适应Any CPU程序的说明
  8. linux离线安装docker + docker-compose
  9. 问题:PyCharm的几种调试方法的区别
  10. 事后Postmortem会议