java.util.AbstractList $ Itr.checkForComodification Тройные события - PullRequest
0 голосов
/ 03 апреля 2011

Я запускаю сервер, и у него есть обработчик событий, который обрабатывает систему синхронизации Когда я запускаю 3 из них подряд, это дает исключение

Exception in thread "Thread-8" java.util.ConcurrentModificationException
        at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
        at java.util.AbstractList$Itr.next(AbstractList.java:343)
        at EventManager.run(EventManager.java:77)
        at java.lang.Thread.run(Thread.java:662)

вот метод, из которого исходит проблема:

EventManager.getSingleton().addEvent( new Event() { 
                public void execute(EventContainer c) {
                    p.createProjectile(p.absY, p.absX, offsetY, offsetX, 1166, 43, 31, 70, p2.playerId);
                    c.stop(); // stops the event from running
                }
            }, 950); // in ms (1 second = 1000 ms)
EventManager.getSingleton().addEvent( new Event() { 
                public void execute(EventContainer c) {
                    p2.applyDAMAGE(misc.random(25));
                    c.stop(); // stops the event from running
                }
            }, 1300); // in ms (1 second = 1000 ms)
            p.secondsTillNextDfsSpecial = 120;
EventManager.getSingleton().addEvent( new Event() { 
                public void execute(EventContainer c) {
                    p.secondsTillNextDfsSpecial--;
                    if (p.secondsTillNextDfsSpecial == 0) {
                        p.canPerformDfsSpecial = true;
                        c.stop(); // stops the event from running
                    }
                }
            }, 1000); // in ms (1 second = 1000 ms)


import java.util.ArrayList;
import java.util.List;

/**
 * Manages events which will be run in the future.
 * Has its own thread since some events may need to be ran faster than the cycle time
 * in the main thread.
 * 
 * @author Graham
 *
 */
public class EventManager implements Runnable {


/**
 * A reference to the singleton;
 */
private static EventManager singleton = null;

/**
 * A list of events that are being executed.
 */
private List<EventContainer> events;

/**
 * Initialise the event manager.
 */
private EventManager() {
    events = new ArrayList<EventContainer>();
}

/**
 * The event manager thread. So we can interrupt it and end it nicely on shutdown.
 */
private Thread thread;

/**
 * Gets the event manager singleton. If there is no singleton, the singleton is created.
 * @return The event manager singleton.
 */
public static EventManager getSingleton() {
    if(singleton == null) {
        singleton = new EventManager();
        singleton.thread = new Thread(singleton);
        singleton.thread.start();
    }
    return singleton;
}

/**
 * Initialises the event manager (if it needs to be).
 */
public static void initialise() {
    getSingleton();
}

/**
 * The waitFor variable is multiplied by this before the call to wait() is made.
 * We do this because other events may be executed after waitFor is set (and take time).
 * We may need to modify this depending on event count? Some proper tests need to be done.
 */
private static final double WAIT_FOR_FACTOR = 0.5;

@Override
/**
 * Processes events. Works kinda like newer versions of cron.
 */
public synchronized void run() {
    long waitFor = -1;
    List<EventContainer> remove = new ArrayList<EventContainer>();

    while(true) {

        // reset wait time
        waitFor = -1;

        // process all events
        for(EventContainer container : events) {
            if(container.isRunning()) {
                if((System.currentTimeMillis() - container.getLastRun()) >= container.getTick()) {
                    container.execute();
                }
                if(container.getTick() < waitFor || waitFor == -1) {
                    waitFor = container.getTick();
                }
            } else {
                // add to remove list
                remove.add(container);
            }
        }

        // remove events that have completed
        for(EventContainer container : remove) {
            events.remove(container);
        }
        remove.clear();

        // no events running
        try {
            if(waitFor == -1) {
                wait(); // wait with no timeout
            } else {
                // an event is running, wait for that time or until a new event is added
                int decimalWaitFor = (int)(Math.ceil(waitFor*WAIT_FOR_FACTOR));
                wait(decimalWaitFor);
            }
        } catch(InterruptedException e) {
            break; // stop running
        }
    }
}

/**
 * Adds an event.
 * @param event The event to add.
 * @param tick The tick time.
 */
public synchronized void addEvent(Event event, int tick) {
    events.add(new EventContainer(event,tick));
    notify();
}

/**
 * Shuts the event manager down.
 */
public void shutdown() {
    this.thread.interrupt();
}

}

1 Ответ

4 голосов
/ 03 апреля 2011

Хорошо, я вижу две проблемы:

  1. Ваши события List не синхронизированы, и вы обращаетесь к нему из разных потоков (одна в EventManager и вторая в первой частикода с addEvent()).

  2. В этом цикле:

    // process all events
    for(EventContainer container : events) {
        ...
    }
    

вы перебираете события List и вы не может добавить новых элементов к нему во время итерации.Я предполагаю, что addEvent() добавляет новые элементы в этот список, поэтому в основном вы не должны вызывать его во время этой итерации.

Обе эти проблемы могут быть решены с помощью CopyOnWriteArrayList , который позволяетдоступ параллельными потоками и , позволяющий безопасно добавлять новые элементы во время итерации (однако новые элементы будут «видны» только в следующей итерации).

Решение:

private EventManager() {
    events = new CopyOnWriteArrayList() ;
}
...