Гарантируется ли, что коллекция Java находится в допустимом и пригодном для использования состоянии после исключения ConcurrentModificationException? - PullRequest
8 голосов
/ 21 марта 2019

Я пишу приложение с графическим интерфейсом пользователя, используя шаблон Графический интерфейс * Immediate Mode , и пользовательский интерфейс работает в потоке, отдельном от механизма, который обеспечивает фактическую функциональность приложения. Поток GUI завершает итерацию по многим спискам объектов, которые концептуально «принадлежат» потоку движка, и эти списки меняются крайне редко. Поток графического интерфейса vsync'ed означает, что он работает на частоте около 60 Гц, а поток двигателя работает на частоте около 200 Гц.

Иногда действия в пользовательском интерфейсе изменяют содержимое коллекций в движке, и у меня есть система передачи сообщений, чтобы публиковать Runnables в потоке движка для выполнения этих мутаций, чтобы гарантировать, что эти мутации не конфликтуют с тем, что происходит в двигателе. Таким образом, я могу гарантировать, что движок всегда видит согласованное представление данных, что для моего приложения очень важно.

Поскольку механизм отвечает за все мутации данных, иногда случается, что механизм изменяет содержимое коллекции во время итерации через GUI, и поскольку эти коллекции являются стандартными коллекциями Java, это предсказуемо и правильно выдает ConcurrentModificationException. Я могу придумать несколько способов решения этой проблемы на высоком уровне:

  1. блокировка с использованием синхронизированной коллекции или блокировки чтения-записи
  2. двойной буфер данных, которые читает поток GUI, и поток GUI переворачивает двойной буфер, когда он завершает рисование кадра
  3. игнорировать CME и прервать рисование остальной части кадра, что будет извлекать частичную информацию для кадра, в котором происходит «плохая» мутация, и просто перейти к следующему кадру

Блокировка сопряжена со значительным ухудшением производительности, и хотя графический интерфейс пользователя иногда останавливается в ожидании получения блокировки из потока двигателя, очень важно, чтобы поток двигателя работал с постоянной скоростью, и даже блокировка R / W может привести к остановке двигателя. Двойная буферизация сопряжена со значительной сложностью, поскольку в каждом фрейме имеется много данных, которые считываются графическим интерфейсом.

Я даю вам все эти предыстории, потому что я знаю, что вариант 3 уродлив, и что мой вопрос в некотором смысле "не правильный вопрос". Javadoc для ConcurrentModificationException даже говорит:

Обратите внимание, что отказоустойчивое поведение не может быть гарантировано, так как, вообще говоря, невозможно сделать какие-либо жесткие гарантии при наличии несинхронизированной параллельной модификации. Отказоустойчивые операции создают исключительную ситуацию ConcurrentModificationException. Следовательно, было бы неправильно писать программу, которая зависела от этого исключения для его корректности: исключение ConcurrentModificationException следует использовать только для обнаружения ошибок.

Но! Я не обеспокоен правильностью GUI для единственного кадра, который был бы испорчен CME. Меня интересует только то, что происходит в следующем кадре. Это приводит к моему вопросу: безопасно ли продолжать использовать коллекцию Java (меня больше всего интересует ответ для ArrayList и HashMap) после того, как ConcurrentModificationException было выброшено из его итератора? Кажется логичным, что это так, но я не могу найти документацию, в которой говорится, что объект все еще будет в пригодном для использования состоянии после выброса CME. Очевидно, что в этот момент итератор является тостом, но я бы хотел проглотить исключение и продолжить использование коллекции.

Ответы [ 4 ]

7 голосов
/ 21 марта 2019

Цитирование части цитаты:

Следовательно, было бы неправильно писать программу, которая зависела от этого исключения для ее корректности: необходимо использовать ConcurrentModificationException только для обнаружения ошибок .

Вывод: Ваш код содержит ошибки и нуждается в исправлении, поэтому не имеет значения, в каком состоянии находится коллекция.

Вы должны никогда получить эту ошибку в действительном коде.Это исключение, которое никогда не должно быть поймано и применено.

6 голосов
/ 21 марта 2019

Для большинства сборников, если у вас есть только один писатель, вы, вероятно, в порядке, но нет никаких гарантий. Если у вас есть несколько писателей в HashMap, вы можете получить поврежденную карту с бесконечным циклом в своей структуре.

Я предлагаю вам использовать ReentrantLock, в котором используется метод tryLock(), если вы хотите поддержать метод "только получение блокировки", если он не используется, чтобы минимизировать блокировку.

Другим способом передачи данных из одного потока в другой является Queue модификационных задач, которые движок может выполнять, когда не занят. Это позволяет всем доступ только через один поток.

Кстати: блокировка / разблокировка занимает около 1 микросекунды, что вряд ли будет иметь для вас большое значение, если вы не сделаете это много.

2 голосов
/ 21 марта 2019

Поток GUI завершает итерацию по многим спискам объектов, которые концептуально «принадлежат» потоку движка

Поскольку движок владеет данными, которые я утверждаючто он не должен открыто делиться этими данными с графическим интерфейсом.

Подумайте о том, чтобы обработчик передавал данные в графический интерфейс, а не графический интерфейс , тянущий (или считывающий напрямую) изструктуры данных двигателей.Таким образом, у двигателя есть полный и исключительный контроль над своими данными, как это, вероятно, и должно быть.

Недостатком является то, что это может потребовать существенного изменения.

Но преимуществаможет быть достаточно значительным, чтобы это оправдать:

  • Не более ConcurrentModificationException
  • Нет необходимости в блокировках
  • Нет необходимости постоянно сканировать данные и перерисовывать, только когдаОбновление говорит о.

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

1 голос
/ 21 марта 2019

Блокировка сопровождается значительным снижением производительности

Это правда? Вы видели, убивает ли производительность элементарные ворота / охрана / замок / семафор? При 60 Гц / 200 Гц это маловероятно.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...