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
This is my approach:
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:
fireTableXxxChanged()
.In the end you would have this chain:
tableModel.fireTableXxxChanged()
)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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With