JTable中的并发问题

发布于 2024-11-07 08:58:38 字数 2858 浏览 0 评论 0原文

我有一个问题,我有一个 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)

想知道解决这个问题的最佳方法是什么。

干杯,
克里斯

I have a problem where I have a JTable and a custom model, with concurrent access problems when the model is modified during the rendering phase. I receive an exception like the following, because I assume that it gets the length of the table, the model is updated, and then it accesses a model element that doesn't exist. The AbstractTableModel needs to reaccess the model using a row / column index during rendering to get the required information, and there doesn't seem to be any locking around this, meaning the data can change freely.

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)

Wondering what the best way to solve this problem is.

Cheers,
Chris

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

一曲爱恨情仇 2024-11-14 08:58:38

这是我的方法:

  • 有一个从后台线程更新的数据模型。您不直接从 Swing 使用此模型。
  • 数据模型通过事件通知其侦听器。这些事件包含所需的一切 - 侦听器不应调用数据模型来检索某些值。 (附注:对于非 GUI 目的,您最终可能希望直接调用数据模型,但对于 Swing,您绝对不想这样做。无论哪种方式,都没有必要 - 事件包含所有内容)这些侦听器之一将依次更新表模型,但在事件调度线程上,仅使用事件中的信息。
  • 然后是表模型,它为 JTable 提供各种 getter(getValueAt、getColumnCount,...)。表模型实际上保存了数据模型的本地缓存副本,并且该副本仅通过传入事件进行更新,这些事件在 EDT 上进行处理 - 因为侦听器在 EDT 上运行。因此,不要直接调用底层数据模型非常重要,因为这是从其他线程更新的 - 当 JTable 想要 X 行的某个单元格的值时,该行可能不再存在。获取实际数据的唯一方法是轮询本地缓存。因此,数据模型的本地副本也在 EDT 上进行操作。这很重要,因为在操作本地副本之后,您通常会调用一些 fireTableXxx() 方法以使所有视图自行更新。由于视图也会在 EDT 上更新自身,因此无法在此时间窗口中操作表模型:任何 invokeLater(...) 将在表刷新完成后有效执行。
  • 视图 JTable 调用 EDT 上 TableModel 的 getter。
  • 注册侦听器后,它将接收所有必要的事件以与数据模型同步。

总之,更新表模型和刷新 JTable(以及其他视图,如果有)的过程是一个原子操作。为了实现这一点,我们有一个单独的缓存模型来支持我们的表,该模型仅在 EDT 上更新。与 Swing 相关的所有内容都变成单线程,并且通过使用 invokeLater(),我们可以确保仅在当前事件完全处理后才处理下一个事件。

如何进一步改进:

  • 将实际的 EDT 模型与 JTable 分开,并通过将调用委托给 EDT 模型来实现 TableModel。这样,您可以在单个 EDT 模型上使用无限的 Swing 侦听器。这很棒,因为 Swing 模型(TableModel、ListModel、ComboBoxModel 等)的实现是非常小的、简单且易于理解的实现,并且满足了 DRY 原则 - 不要重复自己。 EDT 模型代码是集中的,并且可以重用。 Swing 模型成为适配器,并且不存储状态。
  • 每个 Swing 模型都以某种方式注册在 EDT 模型上。
  • EDT 模型通知每个已注册的 Swing 模型。例如,AbstractTableModel 实现将通过调用 fireTableXxxChanged() 来通知正在侦听的 JTable,从而对此类通知作出反应。

最后,您将拥有以下链:

  • Swing 模型之上的视图(例如 JTable)
  • EDT 模型之上的 Swing 模型(例如 AbstractTableModel)
  • 侦听器,侦听 EDT 模型,对 EDT 模型事件做出反应通过发送更高级别的事件(例如tableModel.fireTableXxxChanged()),
  • EDT 模型位于并发模型之上。该模型实际上是一个辅助模型,而不是“业务逻辑状态”参考。它实际上是底层实际模型的快照,在 Swing 组件更新期间提供一致的、不变的状态。因此,EDT 模型是 GUI 层中的辅助模型。
  • 一个监听器,监听并发模型,更新事件调度线程上的 EDT 模型。该侦听器可以将多个同时到达的事件捆绑在一起以提高效率。
  • 并发模型根本不关心任何与 Swing/EDT 相关的事情。这个模型是纯粹的业务逻辑。

这种方法允许您将 GUI 与业务逻辑完全分离(这里认识一下 3 层系统:GUI、业务逻辑和持久性),这是非常强大的。在一个设计良好的系统中,所有命令都在第二层执行,您可以非常轻松地创建多个控制器。例如,在 GUI 中,可以使用 Swing 控件来操纵应用程序状态,还可以创建一个命令行,您只需在其中键入命令。这对于业务逻辑的脚本化/自动化测试以及 GUI 如何对业务逻辑的变化做出反应非常方便。

最终,它得到了回报,但它肯定需要大量额外的工作和认真的思考才能做好。

This is my approach:

  • There is a Data Model that is updated from background threads. You do not use this model directly from Swing.
  • The Data Model notifies its listeners via events. These events contain everything that is needed - no listeners should make a call to the Data Model to retrieve some value. (side note: for non-GUI purposes, you eventually may want to make direct calls on the Data Model, but for Swing you definately don't want to do that. Either way, it isn't necessary - the events contain everything) One of these listeners will in turn update the Table Model, but on the Event Dispatching Thread, using only information from the event.
  • Then there is the Table Model, which provides various getters for the JTable (getValueAt, getColumnCount, ...). The Table Model infact holds a locally cached copy of the Data Model, and this copy is updated only via incoming events, which are processed on the EDT - because the listener is running on the EDT. It is thus very important not to make direct calls to the underlying Data Model, as this is being updated from other threads - by the time the JTable wants a value for some cell at row X, this row may no longer exist. The only way to get to the actual data is polling the local cache. Thus, the local copy of the Data Model is also being manipulated on the EDT. This is important, because after the local copy was manipulated, you typically invoke some fireTableXxx() method in order to let all views update themselves. As the views will update themselves on the EDT as well, the Table Model cannot be manipulated in this time window: any invokeLater(...) will effectively be executed after the table refreshing has finished.
  • The View, JTable, calls the getters on the TableModel on the EDT.
  • Upon registering a listener, it will receive all necessary events to become synchronized with the Data Model.

In summary, the process of updating the Table Model and refreshing the JTable (and other views, if any) is made an atomic operation. To achieve this, we have a separate cache model backing our tabel, which is updated only on the EDT. Everything Swing-related becomes single threaded, and by using invokeLater() we are sure that the next event is processed only after the current event has been fully processed.

How to improve this even further:

  • Separate the actual EDT model from the JTable, and implement a TableModel by delegating the calls to the EDT model. This way, you can use unlimited Swing listeners on a single EDT model. This is great, as the implementation of Swing models (TableModel, ListModel, ComboBoxModel, etc.) are very small straightforward and understandable implementations, and the DRY principle is satisfied - Don't Repeat Yourself. The EDT model code is centralized, and becomes reusable. Swing models become adapters, and do not store state.
  • Each Swing model is somehow registered on the EDT model.
  • The EDT model notifies each registered Swing model. For example, an AbstractTableModel implementation would react to such notification with notifying the JTable that is listening, by calling fireTableXxxChanged().

In the end you would have this chain:

  • A view on top of the Swing model (e.g. a JTable)
  • A Swing model on top of the EDT model (e.g. an AbstractTableModel)
  • A listener, listening on the EDT model, reacting to EDT model events by sending out higher-level events (e.g. tableModel.fireTableXxxChanged())
  • The EDT model on top of the concurrent model. This model is infact a helper model, and is not the "business logic state" reference. It is infact a snapshot of the actual model underneath, providing a consistent, unchanging state during updates of Swing components. The EDT model is thus a helper model in the GUI layer.
  • A listener, listening to the concurrent model, updating the EDT model on the Event Dispatching Thread. This listener may bundle several concurrently arriving events in one for increased efficiency.
  • The concurrent model that simply does not care about anything Swing / EDT related at all. This model is pure business logic.

This approach allows you to completely separate GUI from business logic (recognize the 3 layer system here: GUI, Business Logic and Persistence), which is very powerful. In a well-designed system where all commands are executed in the 2nd layer, you can create multiple controllers very easily. For example, in a GUI, it becomes possible to manipulate the application state using Swing controls, but also to create a command line where you can just type the commands. This would come in very handy for scripted/automated testing of the business logic, and how the GUI reacts on changes in the business logic.

In the end, it pays off, but it definately requires a lot of extra work and hard thinking to do it right.

做个ˇ局外人 2024-11-14 08:58:38

Swing 组件/模型应始终从 AWT 线程更新,而不是从另一个线程更新。

请参阅 SwingUtilities。 invokeLaterSwingWorker 用于长时间运行的任务

Swing component/models should always be updated from the AWT thread, and never from another thread.

See SwingUtilities.invokeLater, and SwingWorker for long running tasks

一影成城 2024-11-14 08:58:38

我建议对所有 TableModel 访问使用 Glazed Lists: http://www.glazedlists.com/

我'我们已经在许多项目中使用它们来处理一些相当繁重的数据,并且它运行得非常完美。它将 TableModel 抽象为 ArrayList,您可以将其包装在 SynchronizedTableLists 和 FilteredLists 中,让您可以非常轻松且安全地完成各种真正复杂的事情。

您还可以添加侦听器并获取对 TableModel 的修改的通知

I'd suggested using Glazed Lists for all TableModel access: http://www.glazedlists.com/

I've used them on a number of projects for some pretty heavy lifting of data and it has worked flawlessly. It abstracts TableModels to an ArrayList which you can wrap in SynchronizedTableLists and FilteredLists that let you do all sorts of really complicated things very easily and safely.

You can also add Listeners and get notified of modifications to the TableModel

纸短情长 2024-11-14 08:58:38

如果您要进行并发访问,您需要同步您的模型。
尝试阅读教程
http://download.oracle.com/javase/tutorial/essential/concurrency/
祝你好运

PS:有时您可以为您的软件想到另一种解决方案,而不是并发访问。另外,为了获得更好的答案,您可以发布一些应用程序的代码。

if you are going to do concurrent access you need to synchronize your model.
try reading the tutorial
http://download.oracle.com/javase/tutorial/essential/concurrency/
good luck

PS: Sometimes you can think of another solution for your software than going for concurrent access. Also, to get better answers you could post some code of your application.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文