您的位置:首页 > 其它

并发下remove集合元素要注意的事情

2017-02-07 00:00 260 查看
摘要: 并发,集合, 删除操作

今天再群里看到大牛们讨论,并发环境下对集合元素做remove操作的注意事项,顺带也学习一下:

1、不要在foreach循环里进行元素的remove/add操作,remove元素请使用iterator方式,如并发操作,还需要对iterator对象加锁

反例:

List<String> strs = new ArrayList<String>();
strs.add("1");
strs.add("2");

for (String str : strs) {
if("1".equals(str)){
strs.remove(str);
}
}

这个反例,在并发执行的时候,不一定会出问题,但不代表一定是安全的。

正确的做法是:

Iterator<String> it = strs.iterator();
while(it.hasNext()){

String temp = it.next();
if(删除元素的条件){
it.remove());
}
}

原因是:java自带的一种迭代器快速失败机制。

---------------------------------------------------------------------------------------------------

迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。

它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。

ConcurrentModificationException不会始终指出对象已经由不同线程并发修改,如果单线程违反了规则,同样也有可能会抛出该异常。

迭代器在调用next()、remove()方法时都是调用checkForComodification()方法,该方法主要就是检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 异常,从而产生fail-fast机制。

fail-fast解决方案:(对于ArrayList)

方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

方案二:使用CopyOnWriteArrayList来替换ArrayList。

CopyOnWriteArrayList所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。

该类产生的开销比较大,但是在两种情况下,它非常适合使用。1:在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。2:当遍历操作的数量大大超过可变操作的数量时。

CopyOnWriterArrayList根本就不会产生ConcurrentModificationException异常,也就是它使用迭代器完全不会产生fail-fast机制。

public boolean add(E paramE) {
ReentrantLock  localReentrantLock = this.lock;
localReentrantLock.lock();
try {
Object[] arrayOfObject1 = getArray();
int i = arrayOfObject1.length;
Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1);
arrayOfObject2[i] = paramE;
setArray(arrayOfObject2);
int j = 1;
return j;
} finally {
localReentrantLock.unlock();
}
}

final void setArray(Object[] paramArrayOfObject) {
this.array = paramArrayOfObject;
}


以上红色的三句代码使得CopyOnWriterArrayList不会抛ConcurrentModificationException异常。他们所展现的魅力就在于copy原来的array,再在copy数组上进行add操作,这样做就完全不会影响COWIterator中的array了。

任何对array在结构上有所改变的操作(add、remove、clear等),CopyOnWriterArrayList都会copy现有的数据,再在copy的数据上修改,这样就不会影响COWIterator中的数据了,修改完成之后改变原有数据的引用即可。同时这样造成的代价就是产生大量的对象,同时数组的copy也是相当有损耗的.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: