ConcurrentModificationException, хотя используется безопасный removeIF

Исключение возникает при попытке перерисовать список объектов после того, как некоторые из них были удалены из списка. Элементы удаляются с помощью безопасного метода Collections.removeIf(...). Исключение происходит в цепочке JPanels repaint().

Важные методы:

public void run() {
    isRunning = true;
    while (isRunning) {
        try {
            tick();
            repaint();
            Thread.sleep(20);
        } catch (Exception e) {
            isRunning = false;
        }
    }
}

Выделение и перекраска объектов разделены на разные методы, каждый из которых содержит циклы.

private void tick() {
    for (THead head : heads) {
        head.tick();
    }
    for (TTail tTail : tails) {
        tTail.tick();
    }
    tail.removeIf(o -> !o.isAlive());
}

TTails можно удалить, если флаг alive больше не установлен.

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    for (TTail tTail : tails) {
        tTail.paint(g2d);
    }
    for (THead head : heads) {
        head.paint(g2d);
    }
}

После установки галочки все объекты перекрашиваются. Здесь находится ConcurrentModificationException. Но я не понимаю почему. Все это выполняется в одном потоке, и никакие вложенные циклы не используются. Для удаления элементов я использую метод removeIf, не изменяя объекты при удалении.

Кроме того, THeads и TTails не наследуются ни от одного суперкласса Swing, поэтому их соответствующие методы paint используют графический объект только для выполнения операции рисования, не нарушая цепочку рисования.

РЕДАКТИРОВАТЬ 1: Теперь у меня есть синхронизированный доступ к объекту pos, который доступен как в paint(), так и в tick().

public abstract class TPanelObject {
    private Point pos;
    private Object lock = new Object();

    public Point getPos() {
        synchronized (lock) {
            return pos;
        }
    }

    public void setPos(Point pos) {
        synchronized (lock) {
            this.pos = pos;
        }
    }
}

Это единственный объект, которым манипулируют во время операции, за исключением самого графического объекта.

Возможно, мне нужно позволить TPanelObject унаследовать от JComponent и добавить объект в саму панель. Может быть, исключение вызвано доступом к объекту Graphics, поскольку он больше не действителен во время перерисовки?

Попробуйте TTail tTail : new ArrayList( tails ) перебрать копию коллекции и избежать исключения, вызванного измененной коллекцией во время перебора.

c0der 20.08.2018 11:31
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
608
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Repaint / paintComponent и т. д. Выполняются в потоке событий¹, ваш метод run() и методы tick() выполняются в другом потоке, поэтому у вас есть несколько потоков, обращающихся к одним и тем же данным.

К сожалению, вам нужно синхронизировать доступ к tails (или иным образом сделать доступ к общим данным безопасным).

¹ Вы, конечно, можете вызывать их из ваших потоков, но EDT будет выполнять рисование по запросу через repaint().


private void tick() {
    for (THead head : heads) {
        head.tick();
    }
    for (TTail tTail : tails) {
        tTail.tick();
    }
    synchronized(tails) {
        tail.removeIf(o -> !o.isAlive());
    }
}

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    synchronized(tails) {
        for (TTail tTail : tails) {
            tTail.paint(g2d);
        }
    }
    for (THead head : heads) {
        head.paint(g2d);
    }
}

paint-метод - единственный внешний доступ к объектам tTail. Только с tick() и paint() они выполняют внутренние операции. Однако они не имеют доступа друг к другу.

Torhan Bartel 20.08.2018 11:40

@TorhanBartel, ваши методы tick() и paintComponent() работают в разных потоках. Если tick() удаляет элемент, пока paintComponent() выполняет итерацию коллекции, вы получите ConcurrentModifcationException. Рисование асинхронно, вы не можете контролировать, когда оно действительно происходит. Звонящий repaint() просто делает заявку на перекраску.

Kayaman 20.08.2018 11:50

@TorhanBartel, вы также должны увидеть из трассировки стека, какой поток генерирует исключение. Бьюсь об заклад, вы доллары на пончики, что это ветка событий AWT.

Kayaman 20.08.2018 11:57

Хорошо, сделал это. Проверьте мою правку. И да, это AWT Thread: D

Torhan Bartel 20.08.2018 12:09

@TorhanBartel. Ты синхронизируешься не с тем. Вы должны синхронизировать доступ к tails. Вы придумываете всевозможные безумные теории, когда я говорил вам, что делать. См. Редактирование для примера кода.

Kayaman 20.08.2018 15:28

Другие вопросы по теме