SwingWorker
is used for the following purposes:
- For running long-running tasks in a different thread so as to prevent the GUI from being unresponsive
- For updating GUI with the results produced by the long-running task at the end of the task through
done()
method.
- For updating GUI from time to time with the intermediate results produced and published by the task with the help of
publish()
and process()
methods.
SwingUtilities.invokeLater()
can perform the above tasks as follows:
- Instead of executing
SwingWorker.execute()
method from the EDT, we can executeExecutorService.submit(new MyRunnable())
as it will also create another thread which can execute long-running task.
- For updating GUI at the end of the task, we can put code (written in
done()
method of case1)SwingUtilites.invokeLater(new RunnableToExecuteDoneMethodCode())
at the end of the task.
- For updating GUI in the middle of the task, we can put code (written in
process()
method of case1)SwingUtilites.invokeLater(new RunnableToExecuteProcessMethodCode())
at the place where we called publish()
method in case1.
|
It looks like you would want to:
-
use SwingWorker when you need to monitor the status of a long-running background process
-
use SwingUtilities.invokeLater if you just want a short task to run but do not need feedback on it. Sort of like a fire-and-forget. Just keep in mind it will run in the AWT event dispatching thread, so your application would not be getting any events handled while the task is running.
|
分类: J2SE2008-12-15 20:24 194人阅读 收藏 举报
Swing API的设计目标是强大、灵活和易用。特别地,我们希望能让程序员们方便地建立新的Swing组件,不论是从头开始还是通过扩展我们所提供的一些组件。
出于这个目的,我们不要求Swing组件支持多线程访问。相反,我们向组件发送请求并在单一线程中执行请求。
本文讨论线程和Swing组件。目的不仅是为了帮助你以线程安全的方式使用Swing API,而且解释了我们为什么会选择现在这样的线程方案。
本文包括以下内容:
单线程规则:Swing线程在同一时刻仅能被一个线程所访问。一般来说,这个线程是事件派发线程(event-dispatching thread)。
规则的例外:有些操作保证是线程安全的。
事件分发:如果你需要从事件处理(event-handling)或绘制代码以外的地方访问UI,那么你可以使用SwingUtilities类的invokeLater()或invokeAndWait()方法。
创建线程:如果你需要创建一个线程??比如用来处理一些耗费大量计算能力或受I/O能力限制的工作??你可以使用一个线程工具类如SwingWorker或Timer。
为什么我们这样实现Swing:我们将用一些关于Swing的线程安全的背景资料来结束这篇文章。
Swing的规则是:
一旦Swing组件被具现化(realized),所有可能影响或依赖于组件状态的代码都应该在事件派发线程中执行。
这个规则可能听起来有点吓人,但对许多简单的程序来说,你用不着为线程问题操心。在我们深入如何撰写Swing代码之前,让我们先来定义两个术语:具现化(realized)和事件派发线程(event-dispatching thread)。
具现化的意思是组建的paint()方法已经或可能会被调用。一个作为顶级窗口的Swing组件当调用以下方法时将被具现化:setVisible(true)、show()或(可能令你惊奇)pack()。当一个窗口被具现化,它包含的所有组件都被具现化。另一个具现化一个组件的方法是将它放入到一个已经具现化的容器中。稍后你会看到一些对组件具现化的例子。
事件派发线程是执行绘制和事件处理的线程。例如,paint()和actionPerformed()方法会自动在事件派发线程中执行。另一个将代码放到事件派发线程中执行的方法是使用SwingUtilities类的invokeLater()方法。
所有可能影响一个已具现化的Swing组件的代码都必须在事件派发线程中执行。但这个规则有一些例外:
有些方法是线程安全的:在Swing API的文档中,线程安全的方法用以下文字标记:
This method is thread safe, although most Swing methods are not.
(这个方法是线程安全的,尽管大多数Swing方法都不是。)
一个应用程序的GUI常常可以在主线程中构建和显示:下面的典型代码是安全的,只要没有(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工作都从主线程转到了事件派发线程,因此前面所讨论的代码实际上是线程安全的。
一个applet的GUI可以在init()方法中构造和显示:现有的浏览器都不会在一个applet的init()和start()方法被调用前绘制它。因而,在一个applet的init()方法中构造GUI是安全的,只要你不对applet中的对象调用show()或setVisible(true)方法。
要顺便一提的是,如果applet中使用了Swing组件,就必须实现为JApplet的子类。并且,组件应该添加到的JApplet内容窗格(content pane)中,而不要直接添加到JApplet。对任何applet,你都不应该在init()或start()方法中执行费时的初始化操作;而应该启动一个线程来执行费时的任务。
下述JComponent方法是安全的,可以从任何线程调用:repaint()、revalidate()、和invalidate()。repaint()和revalidate()方法为事件派发线程对请求排队,并分别调用paint()和validate()方法。invalidate()方法只在需要确认时标记一个组件和它的所有直接祖先。
监听者列表可以由任何线程修改:调用addListenerTypeListener()和removeListenerTypeListener()方法总是安全的。对监听者列表的添加/删除操作不会对进行中的事件派发有任何影响。
注意:revalidate()和旧的validate()方法之间的重要区别是,revalidate()会缓存请求并组合成一次validate()调用。这和repaint()缓存并组合绘制请求类似。
大多数初始化后的GUI工作自然地发生在事件派发线程。一旦GUI成为可见,大多数程序都是由事件驱动的,如按钮动作或鼠标点击,这些总是在事件派发线程中处理的。
不过,总有些程序需要在GUI成为可见后执行一些非事件驱动的GUI工作。比如:
在成为可用前需要进行长时间初始化操作的程序:这类程序通常应该在初始化期间就显示出GUI,然后更新或改变GUI。初始化过程不应该在事件派发线程中进行;否则,重绘组件和事件派发会停止。尽管如此,在初始化之后,GUI的更新/改变还是应该在事件派发线程中进行,理由是线程安全。
必须响应非AWT事件来更新GUI的程序:例如,想象一个服务器程序从可能运行在其他机器上的程序得到请求。这些请求可能在任何时刻到达,并且会引起在一些可能未知的线程中对服务器的方法调用。这个方法调用怎样更新GUI呢?在事件派发线程中执行GUI更新代码。
SwingUtilities类提供了两个方法来帮助你在事件派发线程中执行代码:
invokeLater():要求在事件派发线程中执行某些代码。这个方法会立即返回,不会等待代码执行完毕。
invokeAndWait():行为与invokeLater()类似,除了这个方法会等待代码执行完毕。一般地,你可以用invokeLater()来代替这个方法。
下面是一些使用这几个API的例子。请同时参阅《The Java Tutorial》中的“BINGO example”,尤其是以下几个类:CardWindow、ControlPane、Player和OverallStatusPane。
使用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()的线程不会在调用期间持有任何其他线程可能需要的锁。
这是一个使用invokeAndWait()的例子:
void showHelloThereDialog() throws Exception
{
Runnable showModalDialog = new Runnable()
{
public void run()
{
JOptionPane.showMessageDialog( myMainFrame, "Hello There");
}
};
SwingUtilities.invokeAndWait (showModalDialog);
}
类似地,假设一个线程需要对GUI的状态进行存取,比如文本域的内容,它的代码可能类似这样:
void printTextField()
throws Exception {
final String[] myStrings = new String[2];
Runnable getTextFieldText = new Runnable() {
public void run() {
myStrings[0] = textField0.getText();
myStrings[1] = textField1.getText();
}
};
SwingUtilities.invokeAndWait (getTextFieldText);
System.out.println(myStrings[0] + " " + myStrings[1]);}
如果你能避免使用线程,最好这样做。线程可能难于使用,并使得程序的debug更困难。一般来说,对于严格意义下的GUI工作,线程是不必要的,比如对组件属性的更新。
不管怎么说,有时候线程是必要的。下列情况是使用线程的一些典型情况:
执行一项费时的任务而不必将事件派发线程锁定。例子包括执行大量计算的情况,会导致大量类被装载的情况(如初始化),和为网络或磁盘I/O而阻塞的情况。
重复地执行一项操作,通常在两次操作间间隔一个预定的时间周期。
要等待来自客户的消息。
你可以使用两个类来帮助你实现线程:
SwingWorker:创建一个后台线程来执行费时的操作。
Timer:创建一个线程来执行或多次执行某些代码,在两次执行间间隔用户定义的延迟。
使用SwingWorker类
SwingWorker类在SwingWorker.java中实现,这个类并不包含在Java的任何发行版中,所以你必须单独下载它。
SwingWorker类做了所有实现一个后台线程所需的肮脏工作。虽然许多程序都不需要后台线程,后台线程在执行费时的操作时仍然是很有用的,它能提高程序的性能观感。
SwingWorker´s get() method. Here´s an example of using SwingWorker:
要使用SwingWorker类,你首先要实现它的一个子类。在子类中,你必须实现construct()方法还包含你的长时间操作。当你实例化SwingWorker的子类时,SwingWorker创建一个线程但并不启动它。你要调用你的SwingWorker对象的start()方法来启动线程,然后start()方法会调用你的construct()方法。当你需要construct()方法返回的对象时,可以调用SwingWorker类的get()方法。这是一个使用SwingWorker类的例子:
...// 在main方法中:
final SwingWorker worker = new SwingWorker() {
public Object construct() {
return new expensiveDialogComponent();
}
};
worker.start();
...
// 在动作事件处理方法中:
JOptionPane.showMessageDialog (f, worker.get());
当程序的main()方法调用start()方法,SwingWorker启动一个新的线程来实例化ExpensiveDialogComponent。main()方法还构造了由一个窗口和一个按钮组成的GUI。
当用户点击按钮,程序将阻塞,如果必要,阻塞到ExpensiveDialogComponent创建完成。然后程序显示一个包含ExpensiveDialogComponent的模式对话框。你可以在MyApplication.java找到整个程序。
使用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();
}
...
}
在一个线程中执行所有的用户界面代码有这样一些优点:
组件开发者不必对线程编程有深入的理解:像ViewPoint和Trestle这类工具包中的所有组件都必须完全支持多线程访问,使得扩展非常困难,尤其对不精通线程编程的开发者来说。最近的一些工具包如SubArctic和IFC,都采用和Swing类似的设计。
事件以可预知的次序派发:invokeLater()排队的runnable对象从鼠标和键盘事件、定时器事件、绘制请求的同一个队列派发。在一些组件完全支持多线程访问的工具包中,组件的改变被变化无常的线程调度程序穿插到事件处理过程中。这使得全面测试变得困难甚至不可能。
更低的代价:尝试小心锁住临界区的工具包要花费实足的时间和空间在锁的管理上。每当工具包中调用某个可能在客户代码中实现的方法时(如public类中的任何public和protected方法),工具包都要保存它的状态并释放所有锁,以便客户代码能在必要时获得锁。当控制权交回到工具包,工具包又必须重新抓住它的锁并恢复状态。所有应用程序都不得不负担这一代价,即使大多数应用程序并不需要对GUI的并发访问。
这是的SubArctic Java Toolkit的作者对在工具包中支持多线程访问的问题的描述:
我们的基本信条是,当设计和建造多线程应用程序,尤其是那些包括GUI组件的应用程序时,必须保证极端小心。线程的使用可能会很有欺骗性。在许多情况下,它们表现得能够极好的简化编成,使得设计“专注于单一任务的简单自治实体”成为可能。在一些情况下它们的确简化了设计和编码。然而,在几乎所有的情况下,它们都使得调试、测试和维护的困难大大增加甚至成为不可能。无论大多数程序员所受的训练、他们的经验和实践,还是我们用来帮助自己的工具,都不是能够用来对付非决定论的。例如,全面测试(这总是困难的)在bug依赖于时间时是几乎不可能的。尤其对于Java来说,一个程序要运行在许多不同类型的机器的操作系统平台上,并且每个程序都必须在抢先和非抢先式调度下都能正常工作。
由于这些固有的困难,我们力劝你三思是否绝对有使用线程的必要。尽管如此,有些情况下使用线程是必要的(或者是被其他软件包强加的),所以subArctic提供了一个线程安全的访问机制。本章讨论了这一机制和怎样在一个独立线程中安全地操作交互树。
他们所说的线程安全机制非常类似于SwingUtilities类提供的invokeLater()和invokeAndWait()方法。
分享到:
相关推荐
在Swing编程中,正确使用SwingWorker和顶层容器可以创建出高效且响应迅速的GUI应用。开发者应该遵循Swing的线程规则,避免在EDT中执行长时间任务,利用SwingWorker进行异步处理,以确保用户界面的流畅性和稳定性。...
为了确保Swing组件的操作是在EDT中执行,Swing提供了`SwingUtilities.invokeLater(Runnable)`和`SwingUtilities.invokeAndWait(Runnable)`两个方法来帮助开发者解决线程安全问题。 - **SwingUtilities.invokeLater...
Java Swing线程是Java GUI应用程序开发中的核心概念,特别是在创建响应迅速...通过使用`SwingUtilities.invokeLater()`和`SwingWorker`等工具,可以有效地管理并发,防止阻塞Swing线程,从而提高应用的性能和用户体验。
- **SwingUtilities.invokeLater和SwingUtilities.invokeAndWait**:这两个方法用于确保Swing组件只能在EDT中更新。`invokeLater`会在EDT的末尾执行指定的任务,而`invokeAndWait`则会阻塞当前线程,直到指定的任务...
4. 通过SwingUtilities.invokeLater()或SwingWorker来更新GUI,保证线程安全。 5. 在按钮的ActionListener中触发计时或倒计时过程。 以上就是基于Java Swing实现计时与倒计时的主要步骤和关键知识点。实际编写代码...
我们可以使用SwingUtilities.invokeLater或SwingWorker来确保线程安全的更新UI。 8. **绘制钟表**: 模拟钟表的绘制可能需要使用Graphics2D API,包括画圆、画线和填充颜色等操作。数字钟表的显示则可以直接使用 ...
同时,为了更新用户界面,可能使用了Swing的异步事件调度模型,如SwingUtilities.invokeLater()或SwingWorker,确保在事件调度线程中安全地修改UI组件。 总结起来,"Java Swing 赛马小游戏 线程实例"涵盖了以下关键...
线程管理确保游戏的运行不会阻塞用户界面,通常使用SwingUtilities.invokeLater或SwingWorker来实现。 在弹球游戏中,开发者需要实现以下核心功能: 1. 弹球的初始化:设置初始位置、速度和方向。 2. 撞击检测:...
3. **Runnable 和 Thread**: 如果任务相对简单,也可以直接使用 `Runnable` 和 `Thread` 创建新线程,但在 Swing 中,仍需确保将更新 UI 的代码放入 `SwingUtilities.invokeLater()` 或 `invokeAndWait()` 方法中,...
Java的Thread类或者Runnable接口可以实现这一功能,同时, SwingUtilities.invokeLater 或 SwingWorker 可以确保更新UI的操作在正确的线程中执行。 4. **图形图像处理**:“连连看”的棋盘和棋子图片需要加载和显示...
如果需要在非EDT线程中修改Swing组件,必须使用SwingUtilities的invokeLater或invokeAndWait方法。 SwingWorker是JDK 1.6引入的一个类,用于处理耗时任务。在doInBackground方法中执行计算密集型任务,然后通过...
可以使用SwingUtilities.invokeLater或SwingWorker来确保代码在EDT上执行。 总之,“精通Java Swing程序设计”涵盖了Swing组件的使用、布局管理、事件处理、外观定制、高级组件以及线程管理等多个方面。通过学习...
例如,可以使用javax.swing.Timer类创建定时器,结合SwingUtilities.invokeLater或SwingWorker来实现异步更新,从而创建平滑的过渡效果。还可以利用 javax.swing.Timer和java.awt.event.ActionEvent来控制组件的动态...
6. **SwingUtilities**: 这是Swing提供的一些实用工具类,如invokeLater()和invokeAndWait()方法,它们用于在事件调度线程中执行代码,确保GUI更新的正确性和线程安全性。 7. **SwingWorker**: 为了解决GUI线程阻塞...
Swing提供了SwingUtilities.invokeLater()和SwingWorker类来协助开发者正确地处理多线程。 总的来说,"Swing项目开发实例"可能包括创建基本的GUI组件、布局管理、事件处理、自定义外观以及多线程编程等内容。通过...
- SwingUtilities:提供各种实用方法,如`invokeLater()`和`invokeAndWait()`,用于更新UI线程。 - Icon 和 ImageIcon:用于显示图标和图像资源。 - LookAndFeel:改变应用程序的外观和感觉,实现跨平台的一致性...
可以使用 `SwingUtilities.invokeLater()` 或 `SwingWorker` 来确保线程安全。 8. **代码示例**: 以下是一个简化的示例,展示了如何监听按钮移动并避免重叠: ```java import javax.swing.*; import java.awt....
因此,可能使用了SwingUtilities.invokeLater或SwingWorker来处理后台计算和界面更新。 6. **几何变换**:为了适应窗口缩放或其他视觉效果,可能在绘制过程中应用了平移、缩放等几何变换。 7. **用户交互**:通过...
- **方法二:使用`SwingWorker`类** - Swing提供了一个`SwingWorker`类,它允许在后台线程执行计算,同时提供在EDT中更新界面的能力,通过`doInBackground`方法执行后台任务,`process`和`done`方法则在EDT中运行,...
5. **SwingUtilities.invokeLater**:这是Swing的线程模型,所有对Swing组件的修改都应在这个方法内执行,以保证线程安全。 6. ** NimbusLookAndFeel**:这是Swing的一个现代LookAndFeel,提供了一种统一且现代化的...