JTableModelListener.tableChanged()线程安全吗? [英] JTableModelListener.tableChanged() thread safe?
问题描述
在JTable
线程上的tableChanged()
调用是否安全,因此允许我从另一个线程(例如,完成下载内容)调用它?我想象tableChanged()
只是将一个新的Event放入事件队列,以便Event-Dispatcher-Thread在将来的某个时候更新JTable
,但这添加线程安全吗?
简短的回答不是,它不是线程安全的,对tableChanged
的所有调用都应在事件调度线程的上下文内进行.
如果您需要更新TableModel
,则可以将其从表中断开并在EDT范围内一步(setModel
)进行应用,或者将更新更新到模型并通过EDT将其同步回EDT.使用SwingWorker
或EventQueue.invokeLater
Swing的一般经验法则,假设没有什么是线程安全的,并为此提供保护.
我想象tableChanged()只是将一个新的Event放入事件队列,以便Event-Dispatcher-Thread在将来的某个时候更新JTable,但这添加线程安全吗?
并非所有事件都在事件队列中安排,许多事件仅由组件内的for-next
循环处理,直接循环通过已注册的侦听器,就像TableModel
的fire
事件方法一样. ..
例如,来自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);
}
}
}
这意味着fireTableChanged
方法将在调用它的线程的上下文中执行,并将在同一线程内通知其侦听器.
这意味着,如果您要从其他线程中调用TableModel.setValueAt
,它将调用fireTableCellUpdated
,后者将调用fireTableChanged
,并最终在该线程的上下文中调用tableChanged
.>
作为一个补充说明,您不应直接调用JTable#tableChanged
,它是一个副作用,它是公共的(创建JTable
时内部类不是存在的;)),您应该对表的模型进行修改并允许模型触发事件通知.
已更新...
考虑这个非常基本的测试...
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);
}
}
将输出...
isEventDispatchingThread - false
因为该更新实际上没有在EDT中发生,所以它实际上根本没有被事件队列分派...
已通过EDT实例化和单独的更新线程进行了更新
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();
}
});
}
}
输出...
isEventDispatchingThread - false
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?
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
这篇关于JTableModelListener.tableChanged()线程安全吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!