JTable中的并发问题

时间:2011-05-12 18:48:16

标签: java swing concurrency locking jtable

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

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

干杯,
克里斯

4 个答案:

答案 0 :(得分:4)

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

有关长时间运行的任务,请参阅SwingUtilities.invokeLaterSwingWorker

答案 1 :(得分:2)

这是我的方法:

  • 有一个从后台线程更新的数据模型。您不能直接从Swing使用此模型。
  • 数据模型通过事件通知其听众。这些事件包含所需的一切 - 没有侦听器应该调用数据模型来检索某些值。 (旁注:对于非GUI目的,你最终可能想要直接调用数据模型,但对于Swing,你肯定不想这样做。无论哪种方式,它都是不必要的 - 事件包含所有内容)其中一个侦听器将依次更新表模型,但在事件调度线程上,仅使用事件中的信息。
  • 然后是Table Model,它为JTable提供了各种getter(getValueAt,getColumnCount,...)。 Table Model infact保存了数据模型的本地缓存副本,并且此副本仅通过在EDT上处理的传入事件进行更新 - 因为侦听器在EDT上运行。因此,不要直接调用底层数据模型是非常重要的,因为这是从其他线程更新的 - 当JTable想要X行某个单元格的值时,该行可能不再存在。获取实际数据的唯一方法是轮询本地缓存。因此,数据模型的本地副本也在EDT上被操纵。这很重要,因为在操作本地副本之后,通常会调用一些fireTableXxx()方法,以便让所有视图自行更新。由于视图也会在EDT上自行更新,因此无法在此时间窗口中操作表模型:任何invokeLater(...)将在表刷新完成后有效执行。
  • View,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模型的听众,通过发送更高级别的事件(例如tableModel.fireTableXxxChanged())来回应EDT模型事件
  • 在并发模型之上的EDT模型。这个模型实际上是一个帮助模型,而不是"业务逻辑状态"参考。它实际上是下面实际模型的快照,在更新Swing组件期间提供一致,不变的状态。因此,EDT模型是GUI层中的辅助模型。
  • 监听器,监听并发模型,在事件调度线程上更新EDT模型。该监听器可以将多个同时到达的事件捆绑在一起以提高效率。
  • 并发模型根本不关心Swing / EDT相关的任何内容。这个模型是纯商业逻辑。

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

最后,它会得到回报,但它肯定需要大量的额外工作和艰苦的思考来做正确的事。

答案 2 :(得分:1)

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

我已经在一些项目上使用它们来完成一些相当繁重的数据,并且它运行得很完美。它将TableModels抽象为一个ArrayList,您可以将其包装在SynchronizedTableLists和FilteredLists中,这样可以非常轻松,安全地执行各种非常复杂的操作。

您还可以添加监听器并收到有关对TableModel的修改的通知

答案 3 :(得分:0)

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

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