Swing中的多线程

2016-02-19 14:56 5 1 收藏

只要你有一台电脑或者手机,都能关注图老师为大家精心推荐的Swing中的多线程,手机电脑控们准备好了吗?一起看过来吧!

【 tulaoshi.com - 编程语言 】

  注意:在2000年9月我们修改了这篇文章和它的例子以适用于一个更新版本的SwingWorker类。SwingWorker类的这个版本修正了一些微妙的线程bug。

  Swing API的设计目标是强大、灵活和易用。特别地,我们希望能让程序员们方便地建立新的Swing组件,不论是从头开始还是通过扩展我们所提供的一些组件。

  出于这个目的,我们不要求Swing组件支持多线程访问。相反,我们向组件发送请求并在单一线程中执行请求。

  本文讨论线程和Swing组件。目的不仅是为了帮助你以线程安全的方式使用Swing API,而且解释了我们为什么会选择现在这样的线程方案。

  本文包括以下内容:

  单线程规则:Swing线程在同一时刻仅能被一个线程所访问。一般来说,这个线程是事件派发线程(event-dispatching thread)。

  规则的例外:有些操作保证是线程安全的。

  事件分发:如果你需要从事件处理(event-handling)或绘制代码以外的地方访问UI,那么你可以使用SwingUtilities类的invokeLater()或invokeAndWait()方法。

  创建线程:如果你需要创建一个线程??比如用来处理一些耗费大量计算能力或受I/O能力限制的工作??你可以使用一个线程工具类如SwingWorker或Timer。

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  为什么我们这样实现Swing:我们用一些关于Swing的线程安全的背景资料来结束这篇文章。

  Swing的规则是:

  一旦Swing组件被具现化(realized),所有可能影响或依赖于组件状态的代码都应该在事件派发线程中执行。

  这个规则可能听起来有点吓人,但对许多简单的程序来说,你用不着为线程问题操心。在我们深入如何撰写Swing代码之前,让我们先来定义两个术语:具现化(realized)和事件派发线程(event-dispatching thread)。

(本文来源于图老师网站,更多请访问https://www.tulaoshi.com/bianchengyuyan/)

  具现化的意思是组建的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而阻塞的情况。

来源:https://www.tulaoshi.com/n/20160219/1608384.html

延伸阅读
标签: Java JAVA基础
  Servlet/JSP技术和ASP、PHP等相比,由于其多线程运行而具有很高的执行效率。由于Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的同步问题。然而,很多人编写Servlet/JSP程序时并没有注意到多线程同步的问题,这往往造成编写的程序在少量用户访问时没有任何问题,而在并发用户上升到一定值时,...
Thread 创建线程的两种方法: 1、定义类继承Thread类,覆写类中的run方法,调用类对象的start方法,start方法启动线程,调用run方法。Thread类用于描述线程;该类定义一个功能run,用于存储线程要运行的代码。 2、定义类实现Runnable接口,覆盖Runnable接口中的方法,通过Thread类建立线程对象,将Runnable接口的子类对象作为实际参数传递给T...
标签: Delphi
  优秀的数据库应用应当充分考虑数据库访问的速度问题。通常可以通过优化数据库、优化 查询语句、分页查询等途径收到明显的效果。即使是这样,也不可避免地会在查询时闪现一个带有 SQL符号的沙漏,即鼠标变成了查询等待。最可怜的是用户,他(她)在此时只能无奈地等待。遇到急性子的,干脆在此时尝试 Windows中的其它应用程序,...
多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。 在系统级别内,程序并排执行,程序分配到每个程序的执行时间是基于该程序的所需时间和其他程序的所需时间来决定的。 然而,在每个程序内部,存在一个或者多个执行线程,它同时或在一个几乎同时发生的方式里执行不同的任务。 概要提示: iPhone中的线程应用并不是无...
线程组 线程是被个别创建的,但可以将它们归类到线程组中,以便于调试和监视。只能在创建线程的同时将它与一个线程组相关联。在使用大量线程的程序中,使用线程组组织线程可能很有帮助。可以将它们看作是计算机上的目录和文件结构。 !-- frame contents -- !-- /frame contents -- 线程间发信 ...

经验教程

824

收藏

72
微博分享 QQ分享 QQ空间 手机页面 收藏网站 回到头部