首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >奇数ConcurrentModificationException

奇数ConcurrentModificationException
EN

Stack Overflow用户
提问于 2014-09-14 11:36:21
回答 1查看 2.4K关注 0票数 1

我正在测试一个事件系统,我正在为一个项目编写。在上述项目和测试中,我不接触线程。从字面上讲,我不创建线程,也不使用线程做任何事情。然而,我得到了一个ConcurrentModificationException。

据我所知,在其他情况下,可能会引发此异常。来自CME JavaDoc:

请注意,此异常并不总是指示对象已被不同线程并发修改。如果单个线程发出一系列违反对象契约的方法调用,则该对象可能会抛出此异常。

这是我的测试代码:

代码语言:javascript
复制
TestListener test = new TestListener();
Assert.assertTrue(evtMgr.register(test));
Assert.assertFalse(testBool);
evtMgr.fire(new QuestStartEvent(null));
Assert.assertTrue(testBool);

testBool = false;
evtMgr.unregister(test);
evtMgr.fire(new QuestStartEvent(null));
Assert.assertFalse(testBool);

EventManager看起来是这样的:

代码语言:javascript
复制
public boolean register(Listener listener) {
    return listeners.add(new ListenerHandle(listener));
}

public void unregister(Listener listener) {
    listeners.stream().filter((l) -> l.getListener() == listener)
            .forEach(listeners::remove);
}

public <T extends Event> T fire(T event) {
    listeners.forEach((listener) -> listener.handle(event));
    return event;
}

ConcurrentModificationException在.forEach(listeners::remove);的位置

ListenerHandle看起来是这样的:

代码语言:javascript
复制
private final Listener listener;
private final Map<Class<? extends Event>, Set<MethodHandle>> eventHandlers;

public ListenerHandle(Listener listener) {
    this.listener = listener;
    this.eventHandlers = new HashMap<>();

    for (Method meth : listener.getClass().getDeclaredMethods()) {
        EventHandler eh = meth.getAnnotation(EventHandler.class);
        if (eh == null || meth.getParameterCount() != 1) {
            continue;
        }

        Class<?> parameter = meth.getParameterTypes()[0];
        if (!Event.class.isAssignableFrom(parameter)) {
            continue;
        }

        Class<? extends Event> evtClass = parameter.asSubclass(Event.class);
        MethodHandle handle = MethodHandles.lookup().unreflect(meth);
        Set<MethodHandle> handlers = eventHandlers.get(evtClass);
        if (handlers == null) {
            handlers = new HashSet<>();
            eventHandlers.put(evtClass, handlers);
        }

        handlers.add(handle);
    }
}

public void handle(Event event) {
    Class<? extends Event> clazz = event.getClass();
    Set<MethodHandle> handles = eventHandlers.get(clazz);
    if (handles == null || handles.isEmpty()) {
        return;
    }

    for (MethodHandle handle : handles) {
        handle.invoke(listener, event);
    }
}

(为了提高可读性,尝试捕获量)

以及堆栈跟踪:

代码语言:javascript
复制
java.util.ConcurrentModificationException
    at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1545)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:150)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:173)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at EventManager.unregister(EventManager.java:54)

(第54行是.forEach(listeners::remove);)

EN

回答 1

Stack Overflow用户

发布于 2014-09-14 11:51:05

您将得到一个并发修改异常,因为您在对集合进行迭代的同时对它进行修改。而不是

代码语言:javascript
复制
listeners.stream().filter((l) -> l.getListener() == listener)
         .forEach(listeners::remove);

您应该使用传统的基于Iterator的成语,并在迭代器上而不是在集合上调用remove(),或者首先迭代来收集要删除的对象集,然后在初始迭代完成后一次删除它们:

代码语言:javascript
复制
listeners.removeAll(
   listeners.stream().filter((l) -> l.getListener() == listener)
            .collect(Collectors.toList()));

迭代器方法可能更有效。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25832790

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档