多线程修改了ArrayList , 如何知道被谁修改的?

public void method(){
    ...
    for(Object obj: finalObjects){
        ....
        doSth(obj);
        ....
    }
}

我在主线程遍历 ArrayList 的时候,发生了 java.util.ConcurrentModificationException

已检查 doSth 并没有修改 全局变量 finalObjects,所以我怀疑是某个异步方法在我遍历期间修改了这个变量,由于系统庞大,没法直接查找哪里修改了它,有没有办法在运行时获取,是哪个地方的异步,或者是哪个线程,修改了 finalObjects 导致异常发生?

请教各位大佬 ,谢谢 !!

阅读 5.1k
8 个回答

很感谢各位的回答。只是解决这个问题,如@kevinz 那样,就可以了。
但是我的目的是想跟踪这个变量在这一段代码执行过程中,还有哪个线程修改了它。不过看来是很困难,我是一点头绪都没有。

针对这个问题 LZ 可以深入研究一下 ConcurrentModificationException 产生的原因:
java 的增强 for 循环默认使用的 iterator 迭代器的 hasNext 和 next 来遍历元素。这样便不难理解这个问题的出现原因了。
你在其他地方执行了 remove 或者 add 方法,这个方法只会修改 modCount 值不会修改 expectedModCount 的内容,所以在迭代器调用 next 时候校验就会抛出异常

// ArrayList 907 行
 final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

所以问题找到了解决这个问题就简单了
方案一
如果是 remove 操作,找到你其他地方remove,修改成迭代器的方式

ArrayList<Integer> list = new ArrayList<Integer>();
list.add(2);
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
    Integer integer = iterator.next();
    if(integer==2)
        iterator.remove();   //注意这个地方
}

方案二
直接使用 CopyOnWriteArrayList 替换掉你的 ArrayList,前者在add 和 remove 时候都使用了 lock,很好的避免了这个问题。


小编为订阅号「码匠笔记」号主,先后就职于 ThoughtWorks、阿里巴巴等互联网公司的经验分享,包含但不限于 JAVA、并发编程、性能优化、架构设计、小程序、开源软件等。有兴趣可以关注一波,一起学习、讨论。

public void serverTick()
{
    List worldServers = new ArrayList(MinecraftServer.getServer().worldServers)
    for(WorldServer world : MinecraftServer.getServer().worldServers)
    {
        List playerEntities = new ArrayList(world.playerEntities)
        for(Object player : playerEntities)
        {
            getPlayerData((EntityPlayer)player).tick((EntityPlayer)player);
        }
    }
}

你应该向如上的方式修改你的代码。而不是直接依赖某个对象的属性,最好的做法应该是提供 get 方法在方法的内部返回时拷贝一个新的 list,这样可以保证属性的安全性,保证外部的修改不会影响到内部的属性。

因你目前的实现依赖一个对象的属性,你无法保证它不会被其它地方修改,所以你应该要修改你的循环实现方式。

多庞大? 可以尝试 用ide的全局搜索 查查代码看看

最优解决方案:
Vector 替换 ArrayList

“我偏不”的解决方案:
出现 java.util.ConcurrentModificationException
是因为在 for(X : Xs) 的循环执行过程中,迭代器检测到元素数目有变化
因此,换成 for(int i; i <; i++) 就可以避免这个异常了(当然,如果删除了元素就有空指针问题)

“让一个方法独占cpu直到执行完”,这描述……
我理解应该是这么个意思吧:

public void method(){
    synchronized(finalObjects){
      //……
    }
}

似乎表面上是可以解决问题了
但是每发现个问题都得加个 synchronized,所以,为啥不直接用 Vector

世界是科学的,先看下finalObject是在哪里的,被使用的地方有哪些,另外哪里有额外的线程在跑,你需要知道,然后一个一个的排查. 你这点信息,塞牙缝都不够

有完整的程序吗,我可以帮你分析

有完整的程序吗,可以帮你分析

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题