【问题标题】:Java Concurrency Issue with Iterator and Nested Hashtable迭代器和嵌套哈希表的 Java 并发问题
【发布时间】:2011-01-30 08:05:05
【问题描述】:

早上好,希望能得到比我更了解 Java 的人的帮助。我来自 .NET 背景,负责追踪供应商提供的解决方案中的问题。我相信我已经找到了它,但如果可能的话,我想要第二、第三或第四意见。

我认为正在发生的是第 108 行(在下面的代码块中表示)正在修改在 while 循环外部声明的迭代器(memIter)。他们通过更改在循环内部声明的实例而不是原始对象来修改它,我相信它会抛出,因为在修改后的集合/hastbale 的第二次迭代中调用了“next”。我在这个网站上找到了许多指向这个的线程(http://stackoverflow.com/questions/602636/concurrentmodificationexception-and-a-hashmap)但是因为它修改了一个集合(对不起,如果这些是.net术语)在一个集合中(它从迭代器中项目的属性 hastable 中删除一个成员)我会假设相同的逻辑将适用,但它不是我的空间。另外,如果我的假设是正确的,有人可以提供正确的实现吗?

堆栈

java.util.ConcurrentModificationException 堆栈跟踪: java.util.ConcurrentModificationException 在 java.util.HashMap$HashIterator.nextEntry(HashMap.java:793) 在 java.util.HashMap$ValueIterator.next(HashMap.java:822) 在 xxx.xxxxx.xx.xxxxxxx.end(RoleOrganizer.java:108) 在 (xxxxxxxxx.java:568) 在 xxx.xxxx.xxxxxxx.handleRequest(xxxxHandler.java:74) 在 com.xxxxx.server.JavaInstanceMethod.execute(JavaInstanceMethod.java:33) 在 xx.xxxxxxx.execute(AppServer.java:1469) 在 xxx.xx.executeRequest(xxxxxjava:1269) 在 xxx.xxxxx.server.xxxx.doGet(xxxxx.java:350) 在 javax.servlet.http.HttpServlet.service(HttpServlet.java:690) 在 javax.servlet.http.HttpServlet.service(HttpServlet.java:803) 在 org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290) 在 org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) 在 org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233) 在 org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175) 在 org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:128) 在 org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) 在 org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109) 在 org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:286) 在 org.apache.jk.server.JkCoyoteHandler.invoke(JkCoyoteHandler.java:190) 在 org.apache.jk.common.HandlerRequest.invoke(HandlerRequest.java:283) 在 org.apache.jk.common.ChannelSocket.invoke(ChannelSocket.java:767) 在 org.apache.jk.common.ChannelSocket.processConnection(ChannelSocket.java:697) 在 org.apache.jk.common.ChannelSocket$SocketConnection.runIt(ChannelSocket.java:889) 在 org.apache.tomcat.util.threads.ThreadPool$ControlRunnable.run(ThreadPool.java:690) 在 java.lang.Thread.run(Thread.java:619)

下面的代码显示堆栈错误行号 108

第 87 行 anpublic void end()

{

Iterator iter = new ArrayList(this.m_member.getRoles()).iterator();

while (iter.hasNext())

{

  UserType rt = (UserType)iter.next();
  if (!this.m_roleMap.containsKey(rt.getGID()))
  {
    this.m_member.removeRole(ut);
  }
}
iter = this.m_roleMap.values().iterator();

第 99 行
while (iter.hasNext())

{
  UserType ut = (UserType)iter.next();
  if (ut.isUnique())
  {
    Iterator memIter = this.m_member.doTask().lookUpMembers().iterator();
    while (memIter.hasNext())
    {

第 108 行
StoreMember mem = (StoreMember)memIter.next();

      if (mem.doWork() != this.m_member.getId())
      {
        if ((mem.hasRole(ut)) && (!mem.isFormer()))
        {
          mem.removeRole(ut);
        }
      }
    }
  }

【问题讨论】:

    标签: java multithreading concurrency


    【解决方案1】:

    我认为正在发生的事情是第 108 行(在下面的代码块中表示)正在修改迭代器

    我怀疑您误解了在什么情况下您会收到ConcurrentModificationException

    ConcurrentModificationException 不是由于迭代器的并发修改,而是底层集合的并发修改。

    来自文档:

    当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常。

    例如,通常不允许一个线程在另一个线程对其进行迭代时修改 Collection

    【讨论】:

    • 好的,非常感谢你的回复......所以你说它不是修改后的迭代器......明白了。所以如果我有这个正确的,那是因为正在迭代的对象之一中包含的集合正在这一行被修改 mem.removeRole(ut);这是一个准确的陈述还是我错过了什么?
    • 是的。这正是导致并发修改异常的事情。在两次调用memIter.next(); 之间,您不能修改memIter 迭代的集合(例如删除元素)。 (在这种情况下为this.m_member.doTask().lookUpMembers()。)
    • 如果可能,您可以将所有要删除的项目收集到一个单独的集合中,然后在完成迭代后执行m_member.doTask().lookUpMembers().removeAll(membersToBeRemoved)
    【解决方案2】:

    您不能在迭代集合时对其进行修改。解决方案是使用 Iterator 对象,并直接对其调用 remove 。

    使用 ConcurrentHashMap 会有所帮助,因为它不会引发 ConcurrentModificationException。来自 ConcurrentHashMap:

    检索操作(包括get) 一般不会阻塞,所以可能会重叠 带有更新操作(包括 put 并删除)。检索反映了 最近完成的结果 更新操作持有他们的 发作。对于聚合操作,例如 正如 putAll 和明确的,并发的 检索可能反映插入或 仅删除一些条目。 同样,迭代器和枚举 返回反映状态的元素 在某个点的哈希表或 自创建以来 迭代器/枚举。他们不 抛出并发修改异常。 但是,迭代器被设计为 一次只能由一个线程使用。

    【讨论】:

    • 在迭代器上调用 remove 可能不合适 - removeRole 可能有其他副作用。
    • @Jon:你一天中有多少时间花在 SO 上? :)...我每回答第三个问题,就会有你的评论或回答。对 SO 有什么特殊的亲和力,还是只是狂热? :) 我不能做我的日常工作,也不能在这里花这么多时间。
    • 谢谢大家,我很感激你的反馈......以及它在美国东部时间凌晨 3:50 的速度......太棒了
    • 还有其他时区...比如 CET,那里是 9:50,但无论如何,欢迎您。不要忘记你的根源所在的美好的欧洲:)
    【解决方案3】:

    这听起来确实像是在迭代集合时更改集合的问题,尽管在没有看到doTasklookUpMembers 的情况下准确地跟踪正在发生的事情是很棘手的。 (请注意,相同的逻辑也适用于 .NET。)

    两种可能的修复方法(仅适用一种!):

    • lookUpMembers 的结果复制到lookUpMembers 本身或您使用它的位置。从中创建一个新列表,然后从原始集合中删除项目就没有关系了。
    • 创建要在循环中删除的角色列表,然后再次单独循环以删除它们。

    我个人可能会选择第一个,因为这意味着您应该只需要进行一次更改。调用doTask().lookUpMembers() 的结果听起来不像是由原始集合直接支持的。无论哪种方式,你都应该清楚地记录你在做什么。

    【讨论】:

    • 非常感谢您的回复。主要任务是验证它是否是问题的根源,我认为您帮助我得出结论......再次感谢
    猜你喜欢
    • 1970-01-01
    • 2020-08-18
    • 2013-04-21
    • 2013-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-29
    相关资源
    最近更新 更多