任何人都可以通过ConcurrentModificationException解释我吗?

时间:2013-05-28 14:51:40

标签: java

案例1:这不会导致ConcurrentModificationException?任何人都可以告诉我为什么这不会导致ConcurrentModificationException。

public class UpdatePeople {
    List < People > records = new ArrayList < People > ();

    public class AsyncTask extends AsyncTask < Void, Void, Boolean > {
        List < People > people;

        public AsyncTask(List < People > allergy) {
            this.people = people;
        }@
        Override
        protected Boolean doInBackground(Void...params) {
            List < String > responses = new ArrayList < String > ();
            for (People peopleList: this.people) {

            }

        }

    }
}

案例2:这会导致ConcurrentModificationException,因为我正在尝试访问AsyncThread中不是线程安全的人员列表。我可以让我的人员列表实现CopyOnWriteArrayList这是线程安全的,这应该可行。

public class UpdatePeople {
        List < People > records = new ArrayList < People > ();

        public class AsyncTask extends AsyncTask < Void, Void, Boolean > {
            @
            Override
            protected Boolean doInBackground(Void...params) {
                List < String > responses = new ArrayList < String > ();
                for (People peopleList: records) {

                }

            }

        }
    }
  1. 任何人都可以向我解释case 1中究竟发生了什么。我无法理解这是如何解决ConcurrentModificationException问题的。
  2. 案例2是否将实施从ArrayList改为CopyOnWriteArrayList推荐?
  3. 添加例外:

      

    05-28 20:34:21.073:E / XXX(904):未捕获的例外是:05-28   20:34:21.073:E / XXX(904):java.lang.RuntimeException:发生错误   执行doInBackground时()05-28 20:34:21.073:E / XXX(904):at   android.os.AsyncTask $ 3.done(AsyncTask.java:299)05-28 20:34:21.073:   E / XXX(904):at   java.util.concurrent.FutureTask中$ Sync.innerSetException(FutureTask.java:273)   05-28 20:34:21.073:E / XXX(904):at   java.util.concurrent.FutureTask.setException(FutureTask.java:124)   05-28 20:34:21.073:E / XXX(904):at   java.util.concurrent.FutureTask中$ Sync.innerRun(FutureTask.java:307)   05-28 20:34:21.073:E / XXX(904):at   java.util.concurrent.FutureTask.run(FutureTask.java:137)05-28   20:34:21.073:E / XXX(904):at   android.os.AsyncTask $ SerialExecutor $ 1.run(AsyncTask.java:230)05-28   20:34:21.073:E / XXX(904):at   java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)   05-28 20:34:21.073:E / XXX(904):at   java.util.concurrent.ThreadPoolExecutor中的$ Worker.run(ThreadPoolExecutor.java:569)   05-28 20:34:21.073:E / XXX(904):at   java.lang.Thread.run(Thread.java:856)05-28 20:34:21.073:E / XXX(904):   引起:java.util.ConcurrentModificationException 05-28   20:34:21.073:E / XXX(904):at   的java.util.ArrayList $ ArrayListIterator.next(ArrayList.java:569)

1 个答案:

答案 0 :(得分:6)

一般来说,如果您尝试在迭代时修改集合,则会引发ConcurrentModificationException。例如:

public class Main {

    public static void main(String[] args) 
    throws Exception {

        final List<Object> c1 = new ArrayList<Object>(Arrays.<Object>asList(1, 2, 3));
        for (Object o: c1) c1.remove(o);
    }
}

将在运行时导致java.util.ConcurrentModificationException,因为我们在迭代它时修改列表(NB:这里只涉及一个单独的线程)。这是由列表的迭代器检测到并导致异常。

如果所需的修改是删除刚从迭代器接收的元素,则可以通过使用iterator directly来实现所需的结果(在单线程情况下!)。将for(每个)循环替换为:

final Iterator<Object> iterator = c1.iterator();

while (iterator.hasNext()) {

    final Object o = iterator.next();
    if (satisfiesSomeCriterion(o)) iterator.remove();
}
但是,这对所有修改都不起作用。例如,添加不起作用。以下代码仍然失败:

for (Object o: c1) c1.add(Integer.valueOf(((Integer)o).intValue() + 10));

如果底层集合是List,您可以使用ListIterator而不是普通迭代器来解决此限制:

final ListIterator<Integer> iterator = c1.listIterator();

while (iterator.hasNext()) {

    final Integer o = iterator.next();
    if (satisfiesSomeCriterion(o)) iterator.add(Integer.valueOf(o.intValue() + 10);
}

但请注意,这不等于上面给出的代码(插入元素的其他位置)。另请注意,ListIterator.add是一种可选方法;实现可能会在调用时抛出UnsupportedOperationException

上述所有内容仅适用于单线程外壳。如果您尝试在没有正确同步的情况下同时从多个线程访问同一个集合,则会出现一组全新的问题,因为这不仅可能导致迭代线程中的ConcurrentModificationException,还会破坏完整性该集合的内部数据结构。

您可以做一些事情:

  • 使用并发感知集合(例如您已提及的CopyOnWriteArrayList
  • 通过Collections.synchronizedList...Set...Whatever)换行集合。但请注意,在这种情况下,您仍然需要为迭代提供适当的锁定规则。有关详细信息,请参阅this answer
  • 在将原始集合传递给后台作业之前制作副本(并确保后台作业是使用该副本的唯一线程)
  • 通过synchronized块保护集合的所有用途(例如,使用集合本身作为“监视器”对象,但更好:使用专用的监视器对象)。