Исключение возникает при попытке перерисовать список объектов после того, как некоторые из них были удалены из списка. Элементы удаляются с помощью безопасного метода 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, поскольку он больше не действителен во время перерисовки?




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() они выполняют внутренние операции. Однако они не имеют доступа друг к другу.
@TorhanBartel, ваши методы tick() и paintComponent() работают в разных потоках. Если tick() удаляет элемент, пока paintComponent() выполняет итерацию коллекции, вы получите ConcurrentModifcationException. Рисование асинхронно, вы не можете контролировать, когда оно действительно происходит. Звонящий repaint() просто делает заявку на перекраску.
@TorhanBartel, вы также должны увидеть из трассировки стека, какой поток генерирует исключение. Бьюсь об заклад, вы доллары на пончики, что это ветка событий AWT.
Хорошо, сделал это. Проверьте мою правку. И да, это AWT Thread: D
@TorhanBartel. Ты синхронизируешься не с тем. Вы должны синхронизировать доступ к tails. Вы придумываете всевозможные безумные теории, когда я говорил вам, что делать. См. Редактирование для примера кода.
Попробуйте
TTail tTail : new ArrayList( tails )перебрать копию коллекции и избежать исключения, вызванного измененной коллекцией во время перебора.