ConcurrentModificationException TimerTask

Я получаю ConcurrentModificationException, когда запускаю второй таймер после отмены первого. Оба таймера используют отдельный ArrayList и перебирают его. Никакого удаления/изменения в списке не производится, все равно выбрасывается ConcurrentModificationException.

Это происходит, когда я останавливаю первый таймер и сразу запускаю второй. Если я подожду несколько секунд перед запуском второго таймера, то он работает нормально.

В итоге я сделал копию входящего списка на обоих таймерах, передав копию таймеру, но все равно получаю ошибку. Не уверен, почему, поскольку я перебираю список и просто читаю значения.

Код для таймера №1:

private void timerwork(final List<Object> list) throws IOException {
    timer = new Timer();
    final List<Object> taskList = list;
    timer.scheduleAtFixedRate(new CustomTimerTask(taskList) {
        @Override
        public void run() {
            if (taskList != null && !taskList.isEmpty()) {
                synchronized (taskList) {
                    for (Object o: taskList) {
                        try {
                            // this method takes each object, 
                            // does some logic and writes to a flat file, 
                            // it does not modify the object itself, 
                            // but just reads it and does some calculation 
                            // on some local variables
                            valueIncrementOperation(o);
            } catch (IOException e) {
                timer.cancel();
                timer.purge();
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        //once exited out of the loop, it copies the flat file to another file
        try {
            copyFileUsingFileChannels(source, finaldest);
        } catch (IOException ignore) {
        }
    }
}

public synchronized void valueIncrementOperation(Object o) throws IOException {
    DataInputStream d = new DataInputStream(new FileInputStream(sourcefile));
    DataOutputStream out = new DataOutputStream(new FileOutputStream(tempfile));

    initialValue = Long.parseLong(o.getDefaullt_value());
    String count;
    String t;
    while ((count = d.readLine()) != null) {
        String u = count.toUpperCase();
        String[] z = u.split(" ");

        if (z[0].contains(o.getO())) {
            // .............. *snip* ..............
            out.writeBytes(t + "\n");
        }
    d.close();
    out.close();
    copyFileUsingFileChannels(source, initialDest);
}

Код CustomTimerTask:

public class CustomTimerTask extends TimerTask {
    private List<Object> list = new ArrayList<Object>();

    public CustomTimerTask(List<Object> list) {
        this.list = list;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}

Таймер 2 имеет аналогичную логику: т. е. он копирует входящий список перед передачей на таймер 2.

Тем не менее я получаю эту ошибку:

Exception in thread "Timer-0" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
    at java.util.ArrayList$Itr.next(ArrayList.java:831)
    at java.util.TimerThread.mainLoop(Timer.java:555)
    at java.util.TimerThread.run(Timer.java:505)

Обходной путь - подождать несколько секунд перед запуском timer2. Есть ли лучший способ справиться с этим сценарием?


person yonikawa    schedule 29.11.2013    source источник
comment
очевидный вопрос: что находится в com.comcast.ams.simulator.service.OidTypesDao$2.run(OidTypesDao.java:307)? (очевидный ответ: вы находитесь в цикле foreach, в котором вы удаляете элементы из своего списка.)   -  person njzk2    schedule 29.11.2013
comment
this.list = list; не создает копию списка, а просто создает новую ссылку на тот же список.   -  person David Conneely    schedule 29.11.2013
comment
Простым решением, вероятно, будет заменить ваш список на java.util.concurrent.CopyOnWriteArrayList. Это, вероятно, решит вашу проблему.   -  person Gennaro De Luca    schedule 29.11.2013
comment
@DavidC - я копирую список еще до вызова конструктора таймера final List<Object> taskList = list; и передаю taskList в конструктор   -  person yonikawa    schedule 29.11.2013
comment
измените его на final List<Object> taskList = new ArrayList<Object>(list);, чтобы сделать копию, или используйте реализацию копирования при записи, как упоминал @Gennaro.   -  person David Conneely    schedule 29.11.2013
comment
@GennaroDeLuca: спасибо, братан.. Я тоже об этом думал.. Но я получил исключение приведения класса при преобразовании из arrayList... нужно разобраться..   -  person yonikawa    schedule 29.11.2013
comment
@DavidC - это помогло... final List<Object> taskList = list; на самом деле не делал копию, я полагаю... List<Object> taskList = new ArrayList<Object>(list); -- это работает... Спасибо, братан...   -  person yonikawa    schedule 29.11.2013
comment
вместо этого используйте Vector, он синхронизирован (обрабатывает параллельные операции)   -  person    schedule 29.11.2016


Ответы (1)


Из трассировки стека мы знаем, что исключение генерируется внутри ArrayList. Итак, давайте посмотрим на строки кода, которые обрабатывают Lists. (Лишний код удален для удобства чтения)

Сначала идет вызов метода:

private void timerwork(final List<Object> list) {
    final List<Object> taskList = list;

Это означает, что taskList — это тот самый объект, который был передан (названный list).

Во-вторых, этот List передается в конструктор для CustomTimerTask.

timer.scheduleAtFixedRate(new CustomTimerTask(taskList) {

В-третьих, давайте рассмотрим этот конструктор:

private List<Object> list = new ArrayList<Object>();
public CustomTimerTask(List<Object> list) {
    this.list = list;
}

Таким образом, ваш new ArrayList отбрасывается и заменяется аргументом list, который, как мы знаем, является точно тем же объектом, который изначально был передан timerwork().

Я знаю, ты сказал:

В итоге я сделал копию входящего списка

Но этого не видно ни в одном из ваших опубликованных кодов. Кроме того, мы не знаем (потому что вы не опубликовали) код вызова для timerwork(), но возможно он выглядит примерно так:

List<Object> list = ....
timerwork(list);
timerwork2(list);

Если это так, то мы видим, что обе задачи таймера работают с одним и тем же объектом в разных потоках, что объясняет ConcurrentModificationException.

Простое исправление и хорошая практика, даже если мой диагноз неверен, в конструкторе CustomTimerTask:

private List<Object> list;
public CustomTimerTask(List<Object> list) {
    // CustomTimerTask gets its own copy to play with
    this.list = new ArrayList<Object>(list);
}
person Stewart    schedule 05.02.2018