使用迭代器时如何避免ConcurrentModificationException

Java Collection类是快速失败的,这意味着如果在使用迭代器遍历某个线程的同时更改了Collection,则iterator.next()将抛出ConcurrentModificationException

在多线程以及单线程环境下都可能出现这种情况。

让我们通过以下示例探索这种情况:

import java.util.*;
 
public class IteratorExample {
 
    public static void main(String args[]){
        List<String> myList = new ArrayList<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();
            System.out.println("List Value:"+value);
            if(value.equals("3")) myList.remove(value);
        }
 
        Map<String,String> myMap = new HashMap<String,String>();
        myMap.put("1", "1");
        myMap.put("2", "2");
        myMap.put("3", "3");
 
        Iterator<String> it1 = myMap.keySet().iterator();
        while(it1.hasNext()){
            String key = it1.next();
            System.out.println("Map Value:"+myMap.get(key));
            if(key.equals("2")){
                myMap.put("1","4");
                //myMap.put("4", "4");
            }
        }
 
    }
}

输出为:

List Value:1
List Value:2
List Value:3
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at com.journaldev.java.IteratorExample.main(IteratorExample.java:27)

从输出堆栈跟踪中可以明显看出,当我们调用迭代器next()函数时,异常即将到来。 如果您想知道Iterator如何检查修改,则它的实现存在于AbstractList类中,其中定义了一个int变量modCount,该变量提供了更改列表大小的次数。 该值在每个next()调用中使用,以检查功能checkForComodification()中是否有任何修改。

现在,注释列表部分并再次运行程序。

输出将是:

Map Value:3
Map Value:2
Map Value:4

由于我们正在更新myMap中的现有键值,因此其大小没有更改,并且没有收到ConcurrentModificationException。 请注意,输出结果可能在您的系统中有所不同,因为HashMap键集的排序方式与列表不同。 如果您将在HashMap中添加新键值的语句取消注释,则会导致ConcurrentModificationException。

要在多线程环境中避免ConcurrentModificationException:

1.您可以将列表转换为数组,然后在数组上进行迭代。 这种方法适用于中小型列表,但是如果列表很大,则对性能的影响很大。

2.您可以通过将列表放在同步块中来在锁定时锁定列表。 不建议使用此方法,因为它将停止多线程的好处。

3.如果您使用的是JDK1.5或更高版本,则可以使用ConcurrentHashMap和CopyOnWriteArrayList类。 这是推荐的方法。

要在单线程环境中避免ConcurrentModificationException:

您可以使用迭代器remove()函数从基础集合对象中删除该对象。 但是在这种情况下,您可以从列表中删除同一对象,而不能删除任何其他对象。

让我们使用并发集合类运行示例:

package com.journaldev.java;
 
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
 
public class ThreadSafeIteratorExample {
 
    public static void main(String[] args) {
 
        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();
            System.out.println("List Value:"+value);
            if(value.equals("3")){
                myList.remove("4");
                myList.add("6");
                myList.add("7");
            }
        }
        System.out.println("List Size:"+myList.size());
 
        Map<String,String> myMap = 
             new ConcurrentHashMap<String,String>();
        myMap.put("1", "1");
        myMap.put("2", "2");
        myMap.put("3", "3");
 
        Iterator<String> it1 = myMap.keySet().iterator();
        while(it1.hasNext()){
            String key = it1.next();
            System.out.println("Map Value:"+myMap.get(key));
            if(key.equals("1")){
                myMap.remove("3");
                myMap.put("4", "4");
                myMap.put("5", "5");
            }
        }
 
        System.out.println("Map Size:"+myMap.size());
    }
 
}

输出为:

List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Size:6
Map Value:1
Map Value:null
Map Value:4
Map Value:2
Map Size:4

从上面的示例可以清楚地看出:

1.可以修改Concurrent Collection类,避免ConcurrentModificationException

2.对于CopyOnWriteArrayList ,迭代器不适应列表中的更改,并且可以处理原始列表。

3.对于ConcurrentHashMap ,其行为并不总是相同的。

条件:

if(key.equals("1")){
    myMap.remove("3");

输出为:

Map Value:1
Map Value:null
Map Value:4
Map Value:2
Map Size:4

它正在使用添加了键“ 4”的新对象。 但不是下一个添加的键为“ 5”的对象。

现在,如果我将条件更改为

if(key.equals("3")){
    myMap.remove("2");

输出为:

Map Value:1
Map Value:3
Map Value:null
Map Size:4

在这种情况下,它不考虑新添加的对象。

因此,如果您使用的是ConcurrentHashMap,请避免添加新对象,因为可以根据键集对其进行处理。 请注意,同一程序可以在您的系统中打印不同的值,因为HashMap键集没有任何顺序。

额外的浇头:

for(int i = 0; i<myList.size(); i++){
    System.out.println(myList.get(i));
    if(myList.get(i).equals("3")){
        myList.remove(i);
        i--;
        myList.add("6");
    }
}

如果您正在单线程环境中工作,并且希望您的代码处理列表中额外添加的对象,则可以使用以下代码并避免使用迭代器。

请注意,由于要删除同一对象,所以要减少计数器,如果必须删除下一个或更远的对象,则不需要减少计数器。

自己尝试。

参考:JournalDev上 使用 JCG合作伙伴提供的迭代器时如何避免ConcurrentModificationException

    相关文章:

    相关片段:

    翻译自: https://www.javacodegeeks.com/2011/05/avoid-concurrentmodificationexception.html

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值