奇数ConcurrentModificationException

时间:2014-09-14 11:36:21

标签: java junit junit4 concurrentmodification

我正在测试我正在为项目编写的事件系统。在上述项目和测试中,我不接触线程。从字面上看,我不创建线程或对线程做任何事情。但是,我收到了ConcurrentModificationException。

我知道还有其他情况可能引发此异常。来自CME JavaDoc:

  

请注意,此异常并不总是表示对象具有   由另一个线程同时修改。如果是单线程   发出一系列违反合同的方法调用   一个对象,该对象可能抛出此异常。

这是我的测试代码:

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看起来像这样:

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看起来像这样:

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);
    }
}

(为了便于阅读,尝试捕获)

堆栈跟踪:

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);

1 个答案:

答案 0 :(得分:0)

您正在获得并发修改例外,因为您在迭代它的同时修改了集合。而不是

listeners.stream().filter((l) -> l.getListener() == listener)
         .forEach(listeners::remove);

你应该使用传统的基于Iterator的习惯用法并在迭代器而不是集合上调用remove(),或者首先迭代以收集要删除的对象集,然后在一个对象中删除它们在初始迭代完成后再进行:

listeners.removeAll(
   listeners.stream().filter((l) -> l.getListener() == listener)
            .collect(Collectors.toList()));

迭代器方法可能更有效。