• 【转】Swing多线程编程


    http://blog.csdn.net/shendl/article/details/1498311

    摘要:
    本文论述了怎样开发多线程的Swing程序,从而提高Swing程序的响应速度和性能。
        近期,我将推出一系列研究Swing程序的文章,这也算是为了向Swing这个优秀的GUI库的设计者致敬吧!
    Swing这种优秀的GUI库一直不能占领桌面市场,实在令人费解,今天,我就用我的努力,为java在桌面市场的成功尽我微薄之力吧!
     
     
    Swing的单线程开发机制
    多线程开发,显然要比单线程开发有趣、高效、美妙得多。特别是在Java这种天生支持多线程的语言中,更是如此。可是,Java最重要的组成部分Swing确是单线程的!
    并非只有Swing是单线程的,大多数GUI库都是单线程的。因为,在GUI的事件处理中,事件和处理事件的底层资源是如此的复杂,以至于使用多线程开发,很难避免死锁和资源竞争问题的发生。而且,如果锁定太多系统资源,对GUI的系统性能将会造成消极影响。
    因此,Swing被开发成了一个基于事件队列的单线程编程模型。GUI上的事件,一个个依次在“事件派发线程”上执行,不会发生事件对资源的争夺。
    Java.awt.EventQueue类,就执行这个功能。
    EventQueue 是一个与平台无关的类,它将来自于基础同位体类和受信任的应用程序类的事件列入队列。
    它封装了异步事件指派机制,该机制从队列中提取事件,然后通过对此 EventQueue 调用 dispatchEvent(AWTEvent) 方法来指派这些事件(事件作为参数被指派)。该机制的特殊行为是与实现有关的。指派实际排入到该队列中的事件(注意,正在发送到 EventQueue 中的事件可以被合并)的惟一要求是:
    按顺序。
    也就是说,不允许同时从该队列中指派多个事件。
    指派顺序与它们排队的顺序相同。
    也就是说,如果 AWTEvent A 比 AWTEvent B 先排入到 EventQueue 中,那么事件 B 不能在事件 A 之前被指派。
    一些浏览器将不同基本代码中的 applet 分成独立的上下文,并在这些上下文之间建立一道道墙。在这样的场景中,每个上下文将会有一个 EventQueue。其他浏览器将所有的 applet 放入到同一个上下文中,这意味着所有 applet 只有一个全局 EventQueue。该行为是与实现有关的。有关更多信息,请参照浏览器的文档。
    所有Swing/AWT事件的处理方法,都被放到唯一的“事件派发线程”中执行。
    一般,我们使用EventQueue类的2个方法,将事件处理方法放到“事件派发线程”中执行。
    invokeLater
    public static void invokeLater(Runnable runnable)
    导致 runnable 的 run 方法在 EventQueue 的指派线程上被调用。在所有挂起事件被处理后才发生。
    参数:
    runnable - Runnable,其 run 方法应该在 EventQueue 上同步执行
    从以下版本开始:
    1.2
    另请参见:

    invokeAndWait
    public static void invokeAndWait(Runnable runnable)
                              throws InterruptedException,
                                     InvocationTargetException
    导致 runnable 的 run 方法在 EventQueue 的指派线程上被调用。在所有挂起事件被处理后才发生。在这发生之前调用被阻塞。如果从事件指派线程进行调用,则该方法将抛出 Error。
    参数:
    runnable - Runnable,其 run 方法应该在 EventQueue 上同步执行
    抛出:
    InterruptedException - 如果另一个线程已经中断了该线程
    InvocationTargetException - 如果运行 runnable 时抛出一个 throwable
    从以下版本开始:
    1.2
    另请参见:

    设计Swing的UI组件的执行,一般都需要运行在“事件派发线程”上。
     
     
    Swing单线程开发引起的问题
    Java是一种多线程编程语言。多线程给程序带来了并发的好处。Swing单线程开发的一个问题是,如果在“事件派发线程”上执行的运算太多,那么GUI界面就会停住,系统响应和运算就会非常缓慢。
    既然,“事件派发线程”是为了处理GUI事件而设的,那么,我们只应该把GUI事件处理相关的代码,放在“事件派发线程”中执行。其他与界面无关的代码,应该放在Java其他的线程中执行。
    这样,我们在Swing的事件处理中,仍然使用Swing的单线程编程模型,而其他业务操作均使用多线程编程模型,这就可以大大提高Swing程序的响应和运行速度,充分运用Java多线程编程的优势。
     
    Swing程序的线程
    Swing应用程序的线程,分为两种,一种是“事件派发线程”,实际上只有唯一的一条线程;另一种是一般的Java线程,可以有无数条线程。
    与系统事件处理相关的代码,需要运行在“事件派发线程”中。一般就是Swing的UI组件。Swing组件,由于包含了SwingUI组件,所以常常也需要运行在“事件派发线程”中。
    与业务相关的代码,特别是大计算量,或者涉及到IO,网络,等待资源等耗时的操作,需要放置到一个独立的Java线程中,实现并行运算,提高性能。
     
    Swing程序线程应用示例
    下面,我以一个一般的Swing程序为例,具体说明Swing多线程编程应该怎样进行。
    1,JFrame子类:
    public class DiagramDesignerJFrame extends javax.swing.JFrame {…}
    这是一个JFrame的子类,是一个顶级窗口。顶级窗口,就是一个从操作系统中拿到的窗口。Java可以在这个窗口中使用从操作系统得到的画笔,绘制出Swing需要的GUI。
     
    2,main方法:
     
      
     1   /**
     2      *@paramargs
     3      *            thecommandlinearguments
     4      */
     5     publicstaticvoid main(String args[]) {
     6         /**
     7          *在一般线程中,执行SPring容器的初始化
     8          */
     9         try {
    10             SpringUtil.getCtx();
    11         } catch (BeansException e) {
    12             /*
    13             *
    14             */
    15             e.printStackTrace();
    16         } catch (DocumentException e) {
    17             /*
    18             *
    19             */
    20             e.printStackTrace();
    21         }
    22         java.awt.EventQueue.invokeLater(new Runnable() {
    23             publicvoid run() {
    24                 new DiagramDesignerJFrame().setVisible(true);
    25             }
    26         });
    27     }
    28  
    首先,我们在一般Java线程中,执行Spring容器的初始化。这是非常大量的计算,而且与操作系统事件根本没有关系。
    然后,我们在“事件派发线程”中异步执行对JFrame的子类的实例化和可视化。
    new DiagramDesignerJFrame()这个实例化操作,是在“事件派发线程”中执行的;
    setVisible(true)也是在“事件派发线程”中执行的。
     
     
    控制器中多线程的运用
    Swing是MVC模式设计的典范。其控制器,就是事件监听器,一般实现为内部类。Swing各个组件都可以注册非常多的事件监听器,也就是“控制器”。
    Swing的UI组件,是其界面View。各个Swing组件的Model是其模型。
    一般,用户在Swing的UI组件上执行操作,激发事件,然后由控制器进行处理,修改Swing组件的Model。Model又激发Java事件(而非操作系统事件),使UI根据新的Model值重绘。
    我们在“控制器”中会调用业务代码,这些代码可能会很耗时。调用结束以后,常常又会调用Swing组件的方法,更新Swing应用程序的外观。
    因此,我们应该在Swing的控制器中,把调用业务方法的代码,交给一个新建的一般Java线程去执行。执行完毕之后,再在“事件派发线程”中执行Swing组件更新代码。
        下面是执行“另存为”功能的控制器。它首先保存Swing组件中的数据,然后打开文件选择其对话框,让用户选择要保存到哪个文件中。
     1 /**
     2      *@returnsaveAsActionListener
     3      */
     4     public ActionListener getSaveAsActionListener() {
     5         if (this.saveAsActionListener == null) {
     6             this.saveAsActionListener = new ActionListener() {
     7                 /**
     8                  *响应点击另存为按钮的事件的方法
     9                  */
    10                 publicvoid actionPerformed(ActionEvent e) {
    11                     final SwingWorker worker = new SwingWorker() {
    12  
    13                         @Override
    14                         public Object construct() {
    15                             /*
    16                             *
    17                             */
    18  
    19                         try {
    20                             getJEditorPane1().fireControllerChangeListener();
    21                              return DiagramDesignerJFrame.serviceFinished;
    22                         } catch (DocumentException e1) {
    23                             /*
    24                              *
    25                              */
    26                             e1.printStackTrace();
    27                             JOptionPane.showMessageDialog(
    28                                     DiagramDesignerJFrame.this, "您的输入不符合xml格式要求!"
    29                                             + e1.getMessage());
    30                         } catch (Exception e1) {
    31                             /*
    32                              *
    33                              */
    34                             e1.printStackTrace();
    35                         }
    36                             returnnull;
    37                         }
    38                         /**
    39                          *执行完构造器后,在GUI上异步执行它。
    40                          */
    41                          publicvoid finished() {
    42                              saveAction();
    43                             }
    44                        
    45                     };
    46                     worker.start();
    47                    
    48                
    49  
    50                    
    51  
    52                 }
    53  
    54             };
    55  
    56         }
    57  
    58         returnsaveAsActionListener;
    59     }
    60  
    SwingWorker这个Swing多线程开发的助手类
    上面的例子中用到了SwingWorker这个Swing多线程开发的助手类。在JDK6中,这个类已经作为Swing的一部分。这里,我使用的是JDK5,因此,我是使用了之前SUN创建的SwingWorker类。
    其中,public Object construct() {}
    这个方法中的代码,运行在一个新建的一般Java线程中。我们把耗时的业务方法放在这个方法中执行。
    publicvoid finished() {}
    这个方法中的代码,运行在“事件派发线程”中,是立即返回的。saveAction()显示了一个文件选择其对话框,应该在这里执行!
    只有当construct()执行完毕后,才会执行finished()方法。
     
     
    结语
        上面就是关于Swing多线程开发的一些论述。注意到Swing单线程事件队列开发模型这个事实,你就能够合理的把代码分配到一般Java线程和Swing事件派发线程中,提高Swing应用的性能。
  • 相关阅读:
    安装MySQL ODBC应注意的问题
    AJAX跨域资源共享 CORS 详解
    RealThinClient SDK 学习笔记(1)
    mysql中两表更新时产生的奇葩问题,产生死锁!
    MySQL使用FEDERATED engine建立代理表
    Java Collection与ConcurrentModificationException
    Maven 命令
    多线程12-ManualResetEventSlim
    多线程11-AutoResetEvent
    多线程10-SemaphoreSlim
  • 原文地址:https://www.cnblogs.com/masb/p/2853859.html
Copyright © 2020-2023  润新知