奇数ConcurrentModificationException [英] Odd ConcurrentModificationException

查看:157
本文介绍了奇数ConcurrentModificationException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在测试为项目编写的事件系统.在上述项目和测试中,我不涉及线程.从字面上看,我不创建线程或对线程执行任何操作.但是,我收到了ConcurrentModificationException.

I am testing an event system I am writing for a project. In said project and tests, I do not touch threads. Literally, I do not create a thread or do anything with threads. However, I am getting a ConcurrentModificationException.

我了解在其他情况下可能会引发此异常.从CME JavaDoc:

I understand that there are other situations in which this exception may be thrown. From the CME JavaDoc:

请注意,此异常并不总是表示对象具有 被另一个线程同时修改.如果是单线程 发出违反以下约定的方法调用序列 一个对象,则该对象可能会抛出此异常.

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception.

这是我的测试代码:

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

Where ConcurrentModificationException is at .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);
    }
}

(为方便阅读,尝试剪裁)

(With try-catches cut for readability)

以及堆栈跟踪:

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

推荐答案

您会收到一个并发的修改异常,因为您要在迭代集合的同时修改集合.代替

You're getting a concurrent modification exception because you're modifying the collection at the same time as iterating over it. Instead of

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

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

you should either use a traditional Iterator based idiom and call remove() on the iterator rather than the collection, or first iterate to gather the set of objects you want to remove and then remove them in one go once the initial iteration is finished:

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

迭代器方法可能更有效.

The iterator approach is probably more efficient.

这篇关于奇数ConcurrentModificationException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆