【问题标题】:List ConcurrentModificationException in servlet列出 servlet 中的 ConcurrentModificationException
【发布时间】:2011-11-28 19:29:51
【问题描述】:

对于ArrayList 对象,有很多关于ConcurrentModificationException 的问题,但我还找不到我的问题的答案。

在我的servlet 中,我有一个 ArrayList 作为成员对象:

List myList<Object> = new ArrayList<Object> (...);

该列表必须在用户和会话之间共享。

在servlet 的方法之一method1 中,我需要迭代ArrayList 项,并最终在迭代后添加清除列表。这是一个sn-p:

for (Object o : myList) {
    // read item o
}
myList.clear();

在另一种方法method2 中,我只是将一个新项目添加到列表中。

大多数情况下,该方法会在没有错误的情况下结束其工作。有时,可能由于不同用户同时调用此方法,我得到了著名的java util.ConcurrentModificationException 异常。 我应该将我的列表定义为:

List myList = Collections.synchronizedList(new ArrayList(...));

这就够了,还是我错过了什么?幕后是什么?当存在可能的并发时,容器是否将第二个线程保持在待机状态?

编辑:我已经为一些 cmets 添加了答案。

【问题讨论】:

  • 首先,该列表真的应该在整个 web 应用程序的生命周期内在所有请求/会话之间共享吗?至于您的具体问题,请阅读其 Javadoc 以获得答案:docs.oracle.com/javase/6/docs/api/java/util/…
  • 你能添加你的堆栈跟踪和代码sn-p吗?
  • @BalusC,我添加了更多信息
  • @ApacheFan,我添加了更多信息

标签: java multithreading servlets concurrency


【解决方案1】:

使用同步列表不会解决您的问题。问题的核心是您正在迭代列表并同时对其进行修改。您需要使用互斥机制(同步块、锁等)来确保它们不会同时发生。详细地说,如果你开始:

methodA() {
  iterate over list {
  }
  edit list;
}

methodB() {
  edit list;
}

如果你使用同步列表,你基本上得到的是:

methodA() {
  iterate over list {
  }
  synchronized {
    edit list;
  }
}

methodB() {
  synchronized {
    edit list;
  }
}

但你真正想要的是:

methodA() {
  synchronized {
    iterate over list {
    }
    edit list;
  }
}

methodB() {
  synchronized {
    edit list;
  }
}

【讨论】:

    【解决方案2】:

    仅使用 synchronizedList 就可以使所有方法都成为线程安全的,除了迭代器。

    我会使用CopyOnWriteArrayList。它是线程安全的,不会产生 ConcurrentModificationException。

    【讨论】:

    • CopyOnWriteArrayList 可能是一个非常昂贵的操作,具体取决于列表的大小。如果迭代和修改不包含对 I/O 的突破,那么在处理期间简单地锁定列表会更有效。
    • 您是否建议将其定义为 List myList = new CopyOnWriteArrayList(...) ,这将保证 (Object o : myList) 的紧凑迭代和后续修改的安全?我没有资源问题,我的意思是如果它很贵也没有问题
    • 如果你有数千个元素,它会很昂贵。否则它会做你需要的。
    【解决方案3】:

    ConcurrentModificaitonException 在您尝试修改集合时发生,同时您正在迭代它。我想只有当你执行一些条件操作时才会抛出错误。

    我建议将要添加/删除的值推送到单独的列表中,并在完成迭代后执行添加/删除。

    【讨论】:

    • 由于它是多线程的,您仍然可以在另一个线程迭代时添加/删除。
    【解决方案4】:

    您不仅需要锁定方法访问,还需要锁定您对列表的使用。

    所以如果你像这样分配一个配对对象:

    Object myList_LOCK = new Object();
    

    然后您可以在访问列表时锁定该对象,如下所示:

    synchronized(myList_LOCK) {
    
    //Iterate through list AND modify all within the same lock
    
    }
    

    目前,您要做的唯一锁定是在 List 的各个方法中,这在您的情况下还不够,因为您需要整个迭代和修改序列的原子性。

    您可以使用实际对象 (myList) 来锁定而不是配对对象,但根据我的经验,您最好使用另一个专用对象,因为它可以避免由于对象内部代码而可能出现的意外死锁情况锁定对象本身。

    【讨论】:

    • 原谅我的无知,'配对对象'是什么意思?
    • 只是另一个用作 myList 专用锁的对象。您可以使用列表本身(在 synchronized() 调用中将 myList_LOCK 替换为 myList),但出于上述原因,我通常不建议这样做。
    【解决方案5】:

    这是对 Peter Lawery 答案的补充。但是由于复制不会对您产生太大的负面影响,您可以将复制与同步混合使用。

        private final List<Object> myList = new ArrayList<Object>();
        public void iterateAndClear(){
          List<Object> local = null; 
          synchronized(myList){
              local  = new ArrayList<Object>(myList);
              myList.clear();
          }
          for(Object o  : local){
             //read o
          }
        }
        public void add(Object o){
          synchronized(myList){ 
             myList.add(o);
          }
        }
    

    在这里,您可以迭代 o 元素,而不必担心会被修改(并且在任何类型的同步之外),同时 myList 会被安全地清除并添加到。

    【讨论】:

      猜你喜欢
      • 2013-11-06
      • 2013-02-12
      • 1970-01-01
      • 2013-08-09
      • 1970-01-01
      • 2023-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多