问题
Is the tableChanged()
call on a JTable
thread safe, so that i am allowed to call it from another Thread, which for example finished downloading something? I imagine tableChanged()
to just put a new Event into the Event queue, so that the Event-Dispatcher-Thread will update the JTable
at some point in the future, but is this adding thread safe?
回答1:
Short answer no, it is not thread safe and all calls to tableChanged
should be made from within the context of the Event Dispatching Thread.
If you need to update a TableModel
, either disconnect it from the table and apply it in a single step (setModel
) within the confines of the EDT or sync the updates to the model back to the EDT through the use of a SwingWorker
or EventQueue.invokeLater
General rule of thumb with Swing, assume nothing is thread safe and guard for it.
I imagine tableChanged() to just put a new Event into the Event queue, so that the Event-Dispatcher-Thread will update the JTable at some point in the future, but is this adding thread safe?
Not all events get scheduled on the Event Queue, many are simply processed by a for-next
loop within the component, looping through the registered listeners directly, as is the case for TableModel
's fire
event methods...
For example, from the AbstractTableModel
...
public void fireTableChanged(TableModelEvent e) {
// Guaranteed to return a non-null array
Object[] listeners = listenerList.getListenerList();
// Process the listeners last to first, notifying
// those that are interested in this event
for (int i = listeners.length-2; i>=0; i-=2) {
if (listeners[i]==TableModelListener.class) {
((TableModelListener)listeners[i+1]).tableChanged(e);
}
}
}
This means that the fireTableChanged
method will be executed within the context of the thread that called it, and will notifiy its listeners from within the same thread.
This means that if you were to call TableModel.setValueAt
from a different thread, it would call fireTableCellUpdated
, which would call fireTableChanged
and would eventually call tableChanged
within the context of that thread...
As a side note, you should not be calling JTable#tableChanged
directly, it's public as a side effect (inner classes weren't around when JTable
was created ;)), you should be making modifications to the table's model and allowing the model to trigger the event notifications.
Updated...
Consider this very basic test...
public class Test {
public static void main(String[] args) {
DefaultTableModel model = new DefaultTableModel(new String[]{"One"}, 1);
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
System.out.println("isEventDispatchingThread - " + EventQueue.isDispatchThread());
}
});
model.setValueAt("Test", 0, 0);
}
}
Which will output...
isEventDispatchingThread - false
Because the update did not occur within the EDT, in fact, it was not dispatched by the Event Queue at all...
Updated with instantiation of EDT and separate update thread
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
public class Test {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JLabel("Boo"));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
DefaultTableModel model = new DefaultTableModel(new String[]{"One"}, 1);
model.addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
System.out.println("isEventDispatchingThread - " + EventQueue.isDispatchThread());
}
});
Thread t = new Thread(new Runnable() {
@Override
public void run() {
model.setValueAt("Test", 0, 0);
}
});
t.start();
}
});
}
}
Outputs...
isEventDispatchingThread - false
来源:https://stackoverflow.com/questions/25519336/jtablemodellistener-tablechanged-thread-safe