Java JTable中的并发问题

Java JTable中的并发问题,java,swing,concurrency,locking,jtable,Java,Swing,Concurrency,Locking,Jtable,我有一个问题,我有一个JTable和一个自定义模型,在渲染阶段修改模型时会出现并发访问问题。我收到一个如下所示的异常,因为我假设它获得了表的长度,模型被更新,然后它访问一个不存在的模型元素。AbstractTableModel需要在呈现期间使用行/列索引重新访问模型以获取所需信息,并且似乎没有任何锁定,这意味着数据可以自由更改 Exception in thread "AWT-EventQueue-0" java.lang.IndexOutOfBoundsException: Index: 2,

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

如果要进行并发访问,则需要同步模型。 尝试阅读教程 祝你好运


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

Swing组件/模型应该始终从AWT线程更新,而不能从其他线程更新


请参阅,对于长时间运行的任务

我建议对所有TableModel访问使用釉面列表:

我已经在很多项目中使用了它们来处理一些相当繁重的数据,而且它工作得非常完美。它将表格模型抽象为一个ArrayList,您可以将其封装在同步的表格列表和过滤器列表中,这样您就可以轻松安全地完成各种真正复杂的工作

您还可以添加监听器,并在修改TableModel时得到通知这是我的方法:

  • 有一个从后台线程更新的数据模型。您不能直接从Swing使用此模型
  • 数据模型通过事件通知其侦听器。这些事件包含所需的所有内容—任何侦听器都不应该调用数据模型来检索某些值。(旁注:对于非GUI目的,您最终可能希望对数据模型进行直接调用,但对于Swing,您肯定不希望这样做。无论采用哪种方式,都没有必要-事件包含所有内容)这些侦听器中的一个将依次更新表模型,但在事件调度线程上,仅使用事件中的信息
  • 然后是表模型,它为JTable提供了各种getter(getValueAt、getColumnCount等)。表模型infact保存数据模型的本地缓存副本,该副本仅通过传入事件更新,传入事件在EDT上处理,因为侦听器在EDT上运行。因此,不直接调用底层数据模型是非常重要的,因为这是从其他线程更新的——当JTable需要为第X行的某个单元格指定值时,这一行可能不再存在。获取实际数据的唯一方法是轮询本地缓存。因此,数据模型的本地副本也在EDT上进行操作。这一点很重要,因为在操作本地副本之后,您通常会调用一些fireTableXxx()方法,以便让所有视图更新自己。由于视图也将在EDT上更新自身,因此无法在此时间窗口中操作表模型:在表刷新完成后,将有效地执行任何invokeLater(…)
  • 视图JTable调用EDT上TableModel上的getter
  • 注册侦听器后,它将接收所有必要的事件,以便与数据模型同步
总之,更新表模型和刷新JTable(以及其他视图,如果有的话)的过程是一个原子操作。为了实现这一点,我们有一个单独的缓存模型支持我们的tabel,它只在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模型,通过发送更高级别的事件(例如
    tableModel.fireTableXxxChanged()
    )对EDT模型事件作出反应
  • EDT模型位于并发模型之上。该模型实际上是一个助手模型,而不是“业务逻辑状态”引用。它实际上是底层实际模型的快照,在Swing组件的更新过程中提供了一致、不变的状态。因此,EDT模型是GUI层中的助手模型
  • 侦听器,侦听并发模型,在事件调度线程上更新EDT模型。此侦听器可以将多个并发到达的事件捆绑在一个事件中以提高效率
  • 并发模型根本不关心任何与Swing/EDT相关的东西。此模型是纯业务逻辑
这种方法允许您将GUI与业务逻辑完全分离(在这里识别三层系统:GUI、业务逻辑和持久性),即v