并发下remove集合元素要注意的事情
2017-02-07 00:00
260 查看
摘要: 并发,集合, 删除操作
今天再群里看到大牛们讨论,并发环境下对集合元素做remove操作的注意事项,顺带也学习一下:
1、不要在foreach循环里进行元素的remove/add操作,remove元素请使用iterator方式,如并发操作,还需要对iterator对象加锁
反例:
这个反例,在并发执行的时候,不一定会出问题,但不代表一定是安全的。
正确的做法是:
原因是: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机制。
以上红色的三句代码使得CopyOnWriterArrayList不会抛ConcurrentModificationException异常。他们所展现的魅力就在于copy原来的array,再在copy数组上进行add操作,这样做就完全不会影响COWIterator中的array了。
任何对array在结构上有所改变的操作(add、remove、clear等),CopyOnWriterArrayList都会copy现有的数据,再在copy的数据上修改,这样就不会影响COWIterator中的数据了,修改完成之后改变原有数据的引用即可。同时这样造成的代价就是产生大量的对象,同时数组的copy也是相当有损耗的.
今天再群里看到大牛们讨论,并发环境下对集合元素做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也是相当有损耗的.
相关文章推荐
- 在Js中得到元素的子元素集合注意事项
- 注意list集合remove(index)方法的用法
- STL - 移除(remove)和释放(erase)集合元素
- 为什么Iterator的remove方法可保证从源集合中安全地删除对象,而在迭代期间不能直接删除集合内元素
- List集合remove元素的问题
- 关于集合中在迭代器中用集合的对象删除元素的并发异常问题
- List集合remove元素的问题
- list,set等集合遍历时,不能remove集合中的元素。需要new一个Object或者list,set,里面add需要删除的元素,等集合遍历完了进行remove(Object)或者removeAll(list/set)操作
- List集合remove元素的问题
- 使用for循环删除集合元素需要注意的问题
- List集合remove元素的问题
- 高并发下需要注意的事情1
- 使用Linq求和方法Sum计算集合中多个元素和时应该注意的性能问题
- List集合遍历时修改元素出现并发修改异常总结
- 不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator 方式,如果并发操作,需要对 Iterator 对象加锁
- 不要在foreach 循环里进行集合元素的 remove/add 操作
- list.remove()时出问题,集合的remove方法注意事项
- 如何边遍历集合边删除元素--使用Iterator中的remove()方法
- 集合中的元素无故被擅改,到底是怎么回事
- Java基础知识强化之集合框架笔记19:List集合迭代器使用之 并发修改异常的产生原因 以及 解决方案