代码之家  ›  专栏  ›  技术社区  ›  Olivier J.

在没有ConcurrentModificationException的情况下处理列表上的并发修改

  •  1
  • Olivier J.  · 技术社区  · 12 年前

    我有一个有状态EJB,它调用Web解析页面的EJB无状态方法。

    这是我的状态代码:

    @Override
    public void parse() {
        while(true) {
            if(false == _activeMode) {
                break;
            }
            for(String url : _urls){
                if(false == _activeMode) {
                    break;
                }
                for(String prioritaryUrl : _prioritaryUrls) {
                    if(false == _activeMode)
                        break;
                    boursoramaStateless.parseUrl(prioritaryUrl);
                }
    
                boursoramaStateless.parseUrl(url);
            }
        }
    }
    

    这里没问题。

    我有一些异步调用(使用JMS),它为我的_urls变量(一个List)添加了一些值。目标是在我的无限循环中解析新的url。

    当我试图通过JMS onMessage方法在我的列表中添加新的url时,我收到了ConcurrentModificationException,但它似乎有效,因为这个新的url被解析了。

    当我尝试包装同步块时:

    while(true){
        synchronized(_url){
            // code...
        }
    }
    

    我的新url从未被解析,我希望在for()循环完成后被解析。。。

    所以我的问题是:当List在循环中被访问而没有ConcurrentModificationException时,我如何修改它?

    我只想让两个线程在没有同步块的情况下同时修改一些共享资源。。。

    4 回复  |  直到 12 年前
        1
  •  1
  •   Frank Pavageau    12 年前

    你可能想要 CopyOnWriteArrayList

        2
  •  0
  •   John Dvorak    12 年前

    For (String s : urls) 内部使用迭代器。迭代器检查并发修改,以便对其行为进行良好定义。

    您可以使用 for(int i= ... 环这样,就不会引发异常,并且如果元素只添加到列表的末尾,则仍然可以获得一致的快照(列表在迭代过程中的某个时间存在)。如果列表中的元素四处移动,则可能会丢失条目。

    如果您想使用 synchronised ,您需要在两端同步,但这样会丢失并发读取。

    如果您想要并发访问和一致性快照,您可以使用 java.util.concurrent 包裹 CopyOnWriteArrayList 已经提到了。其他有趣的是 LinkedBlockingQueue ArrayBlockingQueue ( Collection s但不是 List s) 但仅此而已。

        3
  •  0
  •   Olivier J.    12 年前

    好的,谢谢你们。

    所以我做了一些修改。

    1) 添加了迭代器并留下了同步块(在parse()函数内部和addUrl()函数周围,该函数将新的url添加到我的列表中) -->它的工作就像一个魅力,没有启动ConcurrentModificationException

    2) 添加迭代器并删除同步块 -->ConcurrentModificationException仍在启动。。。

    现在,我将阅读更多关于你的答案,并测试你的解决方案。

    再次感谢大家

        4
  •  0
  •   Arjan Tijms UML GURU    12 年前

    首先,忘记 synchronized 当运行到Java EE容器中时。优化线程利用率会困扰容器,而且在集群环境中无法工作。

    其次,你的设计似乎是错误的。您不应该使用JMS更新bean的私有字段。这件事导致 ConcurrentModificationException 。您可能应该修改bean以从数据库中检索集合,并修改MDB以将URL存储到数据库中。

    其他更简单的解决方案如下。 检索当前现有的URL并将其复制到其他集合。然后对该集合进行迭代。当通过JMS更新全局集合时,更新在复制的集合中不可见,因此不会引发异常:

    while(true) {
        for (String url : copyUrls(_prioritaryUrls)) {
           // deal with url
        }
    }
    
    private List<String> copyUrls(List<Stirng> urls) {
        return new ArrayList<String>(urls); // this create copy of the source list
    }
    
    
    //........
    public void onMessage(Message message) {
        _prioritaryUrls.add(((TextMessage)message).getText());
    }