Проблемы параллелизма в JTable - PullRequest
4 голосов
/ 12 мая 2011

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

Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 2, Size: 2
    at java.util.LinkedList.checkElementIndex(LinkedList.java:553)
    at java.util.LinkedList.get(LinkedList.java:474)
    at koku.ui.PlayerList$PlayerInfoTblModel.getValueAt(PlayerList.java:250)
    at javax.swing.JTable.getValueAt(JTable.java:2720)
    at javax.swing.JTable.prepareRenderer(JTable.java:5718)
    at javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2117)
    at javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2019)
    at javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1815)
    at javax.swing.plaf.ComponentUI.update(ComponentUI.java:161)
    at javax.swing.JComponent.paintComponent(JComponent.java:778)
    at javax.swing.JComponent.paint(JComponent.java:1054)
    at javax.swing.JComponent.paintChildren(JComponent.java:887)
    at javax.swing.JComponent.paint(JComponent.java:1063)
    at javax.swing.JViewport.paint(JViewport.java:725)
    at javax.swing.JComponent.paintChildren(JComponent.java:887)
    at javax.swing.JComponent.paint(JComponent.java:1063)
    at javax.swing.JComponent.paintChildren(JComponent.java:887)
    at javax.swing.JComponent.paint(JComponent.java:1063)
    at javax.swing.JComponent.paintToOffscreen(JComponent.java:5206)
    at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:295)
    at javax.swing.RepaintManager.paint(RepaintManager.java:1217)
    at javax.swing.JComponent._paintImmediately(JComponent.java:5154)
    at javax.swing.JComponent.paintImmediately(JComponent.java:4964)
    at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:781)
    at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:739)
    at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:688)
    at javax.swing.RepaintManager.access$700(RepaintManager.java:59)
    at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1632)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:251)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:660)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:211)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

Хотите знать, как лучше решить эту проблему?

Ура,
Chris

Ответы [ 4 ]

4 голосов
/ 12 мая 2011

Компоненты / модели Swing всегда должны обновляться из потока AWT, а не из другого потока.

См. SwingUtilities.invokeLater и SwingWorker для долгосрочных задач

2 голосов
/ 07 февраля 2016

Это мой подход:

  • Существует модель данных, которая обновляется из фоновых потоков.Вы не используете эту модель непосредственно из Swing.
  • Модель данных уведомляет своих слушателей через события.Эти события содержат все, что нужно - ни один слушатель не должен вызывать модель данных для получения какого-либо значения.(примечание: для целей, не связанных с графическим интерфейсом, вы, возможно, в конечном итоге захотите сделать прямые вызовы для Модели данных, но для Swing вы определенно не хотите этого делать. В любом случае, это не обязательно - события содержат все)Один из этих слушателей, в свою очередь, обновит модель таблицы, но в потоке диспетчеризации событий, используя только информацию из события.
  • Затем существует модель таблицы, которая предоставляет различные методы получения для JTable (getValueAt, getColumnCount)....).Инфо табличной модели содержит локально кэшированную копию модели данных, и эта копия обновляется только через входящие события, которые обрабатываются в EDT - потому что слушатель работает в EDT.Таким образом, очень важно не делать прямых вызовов к базовой модели данных, так как она обновляется из других потоков - к тому времени, когда JTable захочет значение для некоторой ячейки в строке X, эта строка может больше не существовать.Единственный способ получить реальные данные - опрос локального кэша.Таким образом, локальная копия модели данных также обрабатывается в EDT.Это важно, потому что после манипуляции с локальной копией вы обычно вызываете метод fireTableXxx (), чтобы позволить всем представлениям обновляться самостоятельно.Поскольку представления будут обновляться и в EDT, моделью таблицы нельзя управлять в этом временном окне: любой invokeLater (...) будет эффективно выполняться после завершения обновления таблицы.
  • Представление,JTable, вызывает геттеры на TableModel на EDT.
  • После регистрации прослушивателя он получит все необходимые события для синхронизации с моделью данных.

В целом, процесс обновления модели таблицы и обновления JTable (идругие виды, если таковые имеются), производится атомарная операция.Для этого у нас есть отдельная модель кэша, поддерживаемая нашей таблицей, которая обновляется только в EDT.Все, что связано с Swing, становится однопоточным, и, используя invokeLater (), мы уверены, что следующее событие будет обработано только после того, как текущее событие будет полностью обработано.

Как еще улучшить это:

  • Отделите фактическую модель EDT от JTable и внедрите TableModel, делегировав вызовы модели EDT.Таким образом, вы можете использовать неограниченное количество слушателей Swing на одной модели EDT.Это замечательно, так как реализация моделей Swing (TableModel, ListModel, ComboBoxModel и т. Д.) - это очень маленькие простые и понятные реализации, и принцип DRY удовлетворяется - не повторяйте себя.Код модели EDT является централизованным и может использоваться повторно.Модели Swing становятся адаптерами и не сохраняют состояние.
  • Каждая модель Swing так или иначе зарегистрирована в модели EDT.
  • Модель EDT уведомляет каждую зарегистрированную модель Swing.Например, реализация AbstractTableModel будет реагировать на такое уведомление, уведомляя JTable, который прослушивает, вызывая fireTableXxxChanged().

. В конце вы получите следующую цепочку:

  • Вид сверху модели Swing (например, JTable)
  • Модель Swing поверх модели EDT (например, AbstractTableModel)
  • Слушатель, слушающий модель EDT,реагирование на события модели EDT путем отправки событий более высокого уровня (например, tableModel.fireTableXxxChanged())
  • Модель EDT поверх параллельной модели.Эта модель фактически является вспомогательной моделью и не является ссылкой на «состояние бизнес-логики».Это фактический снимок действующей модели, предоставляющий согласованное неизменное состояние во время обновления компонентов Swing.Таким образом, модель EDT является вспомогательной моделью на уровне GUI.
  • Слушатель, слушающий параллельную модель, обновляет модель EDT в потоке диспетчеризации событий. Этот слушатель может объединить несколько одновременно прибывающих событий в одно для повышения эффективности.
  • Параллельная модель, которая просто не заботится ни о чем, связанном с Swing / EDT. Эта модель является чистой бизнес-логикой.

Этот подход позволяет полностью отделить графический интерфейс пользователя от бизнес-логики (здесь вы узнаете трехуровневую систему: GUI, бизнес-логика и постоянство), что является очень мощным. В хорошо спроектированной системе, где все команды выполняются на втором уровне, вы можете очень легко создать несколько контроллеров. Например, в графическом интерфейсе становится возможным управлять состоянием приложения, используя элементы управления Swing, а также создавать командную строку, в которой вы можете просто вводить команды. Это очень удобно для скриптового / автоматизированного тестирования бизнес-логики и того, как GUI реагирует на изменения в бизнес-логике.

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

1 голос
/ 13 мая 2011

Я бы предложил использовать Glazed Lists для доступа к TableModel: http://www.glazedlists.com/

Я использовал их в ряде проектов для довольно тяжелого поднятия данных, и это сработало безупречно. Он абстрагирует TableModel в ArrayList, который можно обернуть в SynchronizedTableLists и FilteredLists, что позволяет очень легко и безопасно выполнять всевозможные действительно сложные вещи.

Вы также можете добавить Слушатели и получать уведомления об изменениях в TableModel

0 голосов
/ 12 мая 2011

если вы собираетесь осуществлять параллельный доступ, вам нужно синхронизировать вашу модель. попробуйте прочитать учебник http://download.oracle.com/javase/tutorial/essential/concurrency/ удачи

PS: Иногда вы можете придумать другое решение для вашего программного обеспечения, чем одновременный доступ. Кроме того, чтобы получить лучшие ответы, вы можете опубликовать код своего приложения.

...