Я тестирую систему событий, которую пишу для проекта. В указанном проекте и тестах я не трогаю темы. В буквальном смысле я не создаю треды и ничего не делаю с тредами. Однако я получаю исключение 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);
}
}
(С try-catches вырезаны для удобочитаемости)
И трассировка стека:
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);
)