在Java 遍历集合时,有时会抛出 java.util.ConcurrentModificationException异常。
问题这类异常也被称为 fail-fast,它是Java集合的一种错误检测机制。当线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。
考虑下面的例子:
Iterator<string> it = myList.iterator();
while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
myList.remove(value); // error
}
}
for (Iterator<string> it = myList.iterator(); it.hasNext();) {
String value = it.next();
if (value.equals( "3")) {
myList.remove(value); // error
}
}
for (String value : myList) {
System. out.println( "List Value:" + value);
if (value.equals( "3")) {
myList.remove(value); // error
}
}
三种类型的遍历,都会抛出异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
at java.util.AbstractList$Itr.next(Unknown Source)
fail-fast 有利于帮你查找程序中潜在的问题。
在多线程中,这种异常更加容易出现。
考虑下面例子
public class Test {
static ArrayList<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
Thread thread1 = new Thread(){
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
System.out.println(integer);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
};
Thread thread2 = new Thread(){
public void run() {
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
Integer integer = iterator.next();
if(integer==2)
iterator.remove();
}
};
};
thread1.start();
thread2.start();
}
}
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原理迭代器中的两个重要的方法: hasNext与next。
观察源码:
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
E next = get(cursor);
lastRet = cursor++;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
执行 next 或者 remove方法中都调用了checkForComodification()。我们看下 checkForComodification()方法里面在做些什么事情:
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
抛 ConcurrentModificationException 异常的标准就是`modCount != expectedModCount`
next 或者 remove 操作都会变更 modCount,如果更改与 expectedModCount 不一致就会抛那个并发修改异常!
## 解决方案
### 单线程
```java
// 1 使用Iterator提供的remove方法,用于删除当前元素
for (Iterator<string> it = myList.iterator(); it.hasNext();) {
String value = it.next();
if (value.equals( "3")) {
it.remove(); // ok
}
}
System. out.println( "List Value:" + myList.toString());
// 2 建一个集合,记录需要删除的元素,之后统一删除
List<string> templist = new ArrayList<string>();
for (String value : myList) {
if (value.equals( "3")) {
templist.remove(value);
}
}
// 可以查看removeAll源码,其中使用Iterator进行遍历
myList.removeAll(templist);
System. out.println( "List Value:" + myList.toString());
// 3. 使用线程安全CopyOnWriteArrayList进行删除操作
List<string> myList = new CopyOnWriteArrayList<string>();
myList.add( "1");
myList.add( "2");
myList.add( "3");
myList.add( "4");
myList.add( "5");
Iterator<string> it = myList.iterator();
while (it.hasNext()) {
String value = it.next();
if (value.equals( "3")) {
myList.remove( "4");
myList.add( "6");
myList.add( "7");
}
}
System. out.println( "List Value:" + myList.toString());
// 4. 不使用Iterator进行遍历,需要注意的是自己保证索引正常
for ( int i = 0; i < myList.size(); i++) {
String value = myList.get(i);
System. out.println( "List Value:" + value);
if (value.equals( "3")) {
myList.remove(value); // ok
i--; // 因为位置发生改变,所以必须修改i的位置
}
}
System. out.println( "List Value:" + myList.toString());
多线程
尝试方法:
- 在所有遍历增删地方都加上synchronized或者使用Collections.synchronizedList,虽然能解决问题但是并不推荐,因为增删造成的同步锁可能会阻塞遍历操作。
- 推荐使用ConcurrentHashMap或者CopyOnWriteArrayList。
List<string> myList = new CopyOnWriteArrayList<string>();
myList.add( "1");
myList.add( "2");
myList.add( "3");
myList.add( "4");
myList.add( "5");
new Thread(new Runnable() {
@Override
public void run() {
for (String string : myList) {
System.out.println("遍历集合 value = " + string);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < myList.size(); i++) {
String value = myList.get(i);
System.out.println("删除元素 value = " + value);
if (value.equals( "3")) {
myList.remove(value);
i--; // 注意
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
點擊查看更多內(nèi)容
6人點贊
評論
評論
共同學(xué)習(xí),寫下你的評論
評論加載中...
作者其他優(yōu)質(zhì)文章
正在加載中
感謝您的支持,我會繼續(xù)努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進(jìn)行掃碼打賞哦