线程与Swing- -
转贴自:
http://www.softhouse.com.cn/html/200410/2004102610560200001372.html
本文关于Swing中的多线程,发表于1998年4月。一个月后,我们发表了另一篇文章《使用Swing Worker线程》,该文更深入地讨论了这一主题。要更好地了解多线程在Swing中如何工作,我们建议你把这两篇文章都看一下。 注意:在2000年9月我们修改了这篇文章和它的例子以适用于一个更新版本的SwingWorker类。SwingWorker类的这个版本修正了一些微妙的线程bug。 Swing API的设计目标是强大、灵活和易用。特别地,我们希望能让程序员们方便地建立新的Swing组件,不论是从头开始还是通过扩展我们所提供的一些组件。 出于这个目的,我们不要求Swing组件支持多线程访问。相反,我们向组件发送请求并在单一线程中执行请求。 本文讨论线程和Swing组件。目的不仅是为了帮助你以线程安全的方式使用Swing API,而且解释了我们为什么会选择现在这样的线程方案。 本文包括以下内容:
一旦Swing组件被具现化(realized),所有可能影响或依赖于组件状态的代码都应该在事件派发线程中执行。 这个规则可能听起来有点吓人,但对许多简单的程序来说,你用不着为线程问题操心。在我们深入如何撰写Swing代码之前,让我们先来定义两个术语:具现化(realized)和事件派发线程(event-dispatching thread)。 具现化的意思是组建的paint()方法已经或可能会被调用。一个作为顶级窗口的Swing组件当调用以下方法时将被具现化:setVisible (true)、show()或(可能令你惊奇)pack()。当一个窗口被具现化,它包含的所有组件都被具现化。另一个具现化一个组件的方法是将它放入到 一个已经具现化的容器中。稍后你会看到一些对组件具现化的例子。 事件派发线程是执行绘制和事件处理的线程。例如,paint()和actionPerformed()方法会自动在事件派发线程中执行。另一个将代码放到事件派发线程中执行的方法是使用SwingUtilities类的invokeLater()方法。 所有可能影响一个已具现化的Swing组件的代码都必须在事件派发线程中执行。但这个规则有一些例外:
大多数初始化后的GUI工作自然地发生在事件派发线程。一旦GUI成为可见,大多数程序都是由事件驱动的,如按钮动作或鼠标点击,这些总是在事件派发线程中处理的。 不过,总有些程序需要在GUI成为可见后执行一些非事件驱动的GUI工作。比如:
下面是一些使用这几个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工作,线程是不必要的,比如对组件属性的更新。 不管怎么说,有时候线程是必要的。下列情况是使用线程的一些典型情况:
你可以使用两个类来帮助你实现线程:
使用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(); } ... } 在一个线程中执行所有的用户界面代码有这样一些优点:
我们的基本信条是,当设计和建造多线程应用程序,尤其是那些包括GUI组件的应用程序时,必须保证极端小心。线程的使用可能会很有欺骗性。在许多情况下, 它们表现得能够极好的简化编成,使得设计"专注于单一任务的简单自治实体"成为可能。在一些情况下它们的确简化了设计和编码。然而,在几乎所有的情况下, 它们都使得调试、测试和维护的困难大大增加甚至成为不可能。无论大多数程序员所受的训练、他们的经验和实践,还是我们用来帮助自己的工具,都不是能够用来 对付非决定论的。例如,全面测试(这总是困难的)在bug依赖于时间时是几乎不可能的。尤其对于Java来说,一个程序要运行在许多不同类型的机器的操作 系统平台上,并且每个程序都必须在抢先和非抢先式调度下都能正常工作。 由于这些固有的困难,我们力劝你三思是否绝对有使用线程的必要。尽管如此,有些情况下使用线程是必要的(或者是被其他软件包强加的),所以subArctic提供了一个线程安全的访问机制。本章讨论了这一机制和怎样在一个独立线程中安全地操作交互树。 他们所说的线程安全机制非常类似于SwingUtilities类提供的invokeLater()和invokeAndWait()方法。 |
出处: Java原创社区 FooSleeper 翻译 日期:2004-10-26 |
- 作者: mahaixing 2005年03月10日, 星期四 00:10 加入博采
Trackback
你可以使用这个链接引用该篇文章 http://publishblog.blogchina.com/blog/tb.b?diaryID=903913
发布人: |
邮箱: |
主 页: |
发布人: | 邮箱: | ||
主 页: | |||
评论内容: | |||