【问题标题】:Java: Why ConcurrentModificationException occur with a synchronized list?Java:为什么同步列表会发生 ConcurrentModificationException?
【发布时间】:2014-06-19 02:14:42
【问题描述】:

使用此代码:

public class SynchroApp {

    public static void main(String[] args) {

        final List<String> unsyList = new ArrayList<>();
        final List<String> syList = Collections.synchronizedList(unsyList);

        TimerTask changeList = new TimerTask() {
            boolean addElem = false;

            @Override
            public void run() {
                // add / remove elements to keep size between 2 and 9
                if (syList.size() < 2)
                    addElem = true;
                else if (syList.size() > 8)
                    addElem = false;
                if (addElem)
                    syList.add(String.valueOf(System.currentTimeMillis()));
                else
                    syList.remove(0);
            }
        };

        TimerTask reverseList = new TimerTask() {
            @Override
            public void run() {
                try {
                    for (String s : syList)
                        s = new StringBuffer(s).reverse().toString();
                } catch (Exception e) {
                    System.out.println("Exception: " + e);
                }
            }
        };

        new Timer().scheduleAtFixedRate(changeList, 0L, 30L);
        new Timer().scheduleAtFixedRate(reverseList, 0L, 20L);
    }
}

为什么我仍然在Iterator.next 上收到一些ConcurrentModificationException

编辑:reverseList 中列表元素的更新不起作用(如 cmets 中所述)。此代码应按预期工作:

for (int i = 0; i < syList.size(); i++)
    syList.set(i, new StringBuffer(syList.get(i)).reverse().toString());

【问题讨论】:

  • 我在您发布的代码中没有看到next 调用。但是请注意,reverseListrun 中的循环目前没有做任何事情(Java 字符串是不可变的)。
  • 您不能在使用迭代器的同时修改列表的内容。迭代器将意识到对列表进行了更改并抛出此异常。
  • @kviiri 即使类是可变的,它也不会做任何事情,因为它使用了一个临时变量。
  • next 隐含在 (String s : syList) 中。我认为 s 是用 reverse() 的结果更新的,但这只是一个例子。
  • @Approachingminimums,是的,s 是对列表元素的引用,但您没有更改列表的元素,而是更改(本地)引用以完全引用完全不同的字符串.由于字符串是不可变的,因此您需要删除旧字符串并添加一个新字符串来实际替换某些内容。如果字符串是可变的,您可以执行 s.replace(...) 之类的操作,但 s = "something" 仍然行不通。

标签: java arraylist iterator concurrentmodification


【解决方案1】:

因为您在迭代列表时正在修改列表。

请注意,同步列表仅使其每个方法及其迭代器的方法同步。在同步列表上进行迭代仍然是一个非原子操作,它涉及对同步方法的多次调用。如果你想让整个迭代原子化,你必须明确地同步它,使用列表本身作为锁:

synchronized (syList) {
    for (String s : syList) {
        s = new StringBuffer(s).reverse().toString();
    }
}

【讨论】:

    【解决方案2】:

    即使是大多数同步集合也不喜欢修改和迭代器在一起。来自 Collections.synchronizedList 的 API 描述:

    用户必须手动同步返回的 迭代时列出:

    List list = Collections.synchronizedList(new ArrayList()); ... synchronized (list) { Iterator i = list.iterator(); // Must be in synchronized block while (i.hasNext()) foo(i.next()); }

    另外:您可以改用 java.concurrent 中的集合。它们通常有更精细的同步方法。

    【讨论】:

    • 感谢您参考 API 文档。
    【解决方案3】:

    迭代器和修改器都必须使用 synchronizedList() 才能在对象上发生适当的锁定/同步。

    【讨论】:

      猜你喜欢
      • 2014-09-26
      • 2020-10-21
      • 2021-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-18
      • 1970-01-01
      • 2020-11-08
      相关资源
      最近更新 更多