题记:在做c/s的时候经常需要使用到Jtable来显示数据,但是频繁的对jtable进行操作偶尔会出现返回的数据出现串行显示问题,这个问题的根源就是java提供的事件派发线程没有得到充分使用和发挥!下面就针对开发中出现的问题做如下总结,供大家分享!
1.Swing组件的非线程安全性
Swing组件基本上是非线程安全的,也就是说,Swing组件对多线程的访问并没有作同步处理,出于对Swing的速度、灵活、扩展和死锁问题方面的考虑。Swing API的设计目标是强大、灵活和易用。特别地,Swing API设计者希望能让程序员们方便地建立新的Swing组件,不论是从头开始还是通过扩展他们所提供的一些组件。出于这个目的,Swing API设计者不要求Swing组件支持多线程访问。相反,他们提倡向组件发送请求并在单一线程中执行请求。
例外:
a)只有少数的Swing方法是线程安全的 Swing API有特别注明:
JTextComponent.setText();
JTextArea.insert();
JTextArea.append();
JTextArea.replaceRange();
JComponent类中的repaint()、revalidate()。repaint()和revalidate()方法为事件派发线程对请求排队,并分别调用paint()和validate()方法。
b)Swing中的监听器的增加和删除
大多数初始化后的GUI工作自然地发生在事件派发线程。一旦GUI成为可见,大多数程序都是由事件驱动的,如按钮动作或鼠标点击,这些总是在事件派发线程中处理的。调用addListenerTypeListener()和removeListenerTypeListener()方法总是安全的。对监听者列表的添加/删除操作不会对进行中的事件派发有任何影响。
c)在调用了setVisible(true)或pack()之前,操作Swing组件。
如下面的代码
public class MyApplication
{
public static void main(String[] args)
{
JFrame f = new JFrame("Labels"); // 在这里将各组件
// 加入到主框架……
f.pack();
f.show();
// 不要再做任何GUI工作……
}
}
上面所示的代码全部在“main”线程中运行。对f.pack()的调用使得JFrame以下的组件都被重新刷新了。这意味着,f. show()调用是不安全的且应该在事件派发线程中执行。尽管如此,只要程序还没有一个看得到的GUI,JFrame或它的里面的组件就几乎不可能在f. show()返回前收到一个paint()调用。因为在f.show()调用之后不再有任何GUI代码,于是所有GUI工作都从主线程转到了事件派发线程,因此前面所讨论的代码实际上是线程安全的。
2.Swing组件使用多线程原因
我们往往需要在Swing组件显示完成后(在setVisible、pack、paint等之后),实时的不断的向Swing组件设置和修改原来的属性,组件更新和绘制来达到显示实时效果的目的,通过一个或者多个线程来完成这一目的。
在成为可用前需要进行长时间初始化操作的程序:这类程序通常应该在初始化期间就显示出GUI,然后经过多线程更新或改变GUI。注意,初始化过程不应该在事件派发线程中进行;否则,重绘组件和事件派发会停止。尽管如此,在初始化之后,GUI的更新/改变还是应该在事件派发线程中进行,理由是线程安全。
3.使用Swing线程的原则
单线程规则:
Swing线程在同一时刻仅能被一个线程所访问。一般来说,这个线程是事件派发线程(event-dispatching thread),Swing组件显示后,Swing会创建一个线程―――事件分派线程,所有的事件通知和响应,如actionPerform(),paintComponent()等都是在此线程中统一调度的,这些事件放在一个事件队列中,由事件派发线程统一调度,在后台运行,对程序员是不可见的。一旦Swing组件显示后,所有可能影响或依赖于组件状态的代码都应该在事件派发线程中执行。
如果你需要从事件处理(event-handling)或绘制代码以外的地方访问UI,那么你可以使用SwingUtilities类的invokeLater()或invokeAndWait()方法。
4.Swing组件使用多线程的解决方案
a) 单线程原则
将操作Swing组件的多个线程统一放到Swing的一个事件派发线程中执行
SwingUtilities类提供了两个方法多个操作线程在事件派发线程中执行:
invokeLater():要求在事件派发线程中执行某些代码。这个方法会立即返回,不会等待代码执行完毕。
invokeAndWait():行为与invokeLater()类似,除了这个方法会等待代码执行完毕。一般地,你可以用invokeLater()来代替这个方法。
你可以从任何线程调用invokeLater()方法以请求事件派发线程运行特定代码。你必须把要运行的代码放到一个Runnable对象的run()方法中,并将此Runnable对象设为invokeLater()的参数。invokeLater()方法会立即返回,不等待事件派发线程执行指定代码。
使用invokeLater()方法例子如下:
Runnable doWorkRunnable = new Runnable()
{
public void run()
{
doWork();
}
};
SwingUtilities.invokeLater(doWorkRunnable);
使用invokeAndWait()方法:
invokeAndWait()方法和invokeLater()方法很相似,除了invokeAndWait()方法会等事件派发线程执行了指定代码才返回。在可能的情况下,你应该尽量用invokeLater()来代替invokeAndWait()。如果你真的要使用invokeAndWait(),请确保调用invokeAndWait()的线程不会在调用期间持有任何其他线程可能需要的锁。
例子如下:
void showHelloThereDialog() throws Exception
{
Runnable showModalDialog = new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog( myMainFrame, "Hello There");
}
};
SwingUtilities.invokeAndWait (showModalDialog);
}
b) 放入事件分派线程中执行
如使用Timer类
Timer类通过一个ActionListener来执行或多次执行一项操作。你创建定时器的时候可以指定操作执行的频率,并且你可以指定定时器的动作事件的监听者(action listener)。启动定时器后,动作监听者的actionPerformed()方法会被(多次)调用来执行操作。
定时器动作监听者(action listener)定义的actionPerformed()方法将在事件派发线程中调用。这意味着你不必在其中使用invokeLater()方法。
这是一个使用Timer类来实现动画循环的例子:
public class AnimatorApplicationTimer
extends JFrame implements ActionListener {
...//在这里定义实例变量
Timer timer;
public AnimatorApplicationTimer(...) {
... // 创建一个定时器来
// 来调用此对象action handler。
timer = new Timer(delay, this);
timer.setInitialDelay(0);
timer.setCoalesce(true);
...
}
public void startAnimation() {
if (frozen) {
// 什么都不做。应用户要求
// 停止变换图像。
} else {
// 启动(或重启动)动画!
timer.start();
}
}
public void stopAnimation() {
// 停止动画线程。
timer.stop();
}
public void actionPerformed (ActionEvent e)
{
// 进到下一帧动画。
frameNumber++;
// 显示。
repaint();
}
...
}