zoukankan      html  css  js  c++  java
  • Swing多线程

    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 和 invokeAndWait
    
    
    设计Swing的UI组件的执行,一般都需要运行在“事件派发线程”上。
    Swing单线程开发引起的问题
    Java是一种多线程编程语言。多线程给程序带来了并发的好处。Swing单线程开发的一个问题是,如果在“事件派发线程”上执行的运算太多,那么GUI界面就会停住,系统响应和运算就会非常缓慢。
    既然,“事件派发线程”是为了处理GUI事件而设的,那么,我们只应该把GUI事件处理相关的代码,放在“事件派发线程”中执行。其他与界面无关的代码,应该放在Java其他的线程中执行。
    这样,我们在Swing的事件处理中,仍然使用Swing的单线程编程模型,而其他业务操作均使用多线程编程模型,这就可以大大提高Swing程序的响应和运行速度,充分运用Java多线程编程的优势。
    下面用实例慢慢的接触这个过程

    现在我们要做一个简单的界面。

    包括一个进度条、一个输入框、开始和停止按钮。

    需要实现的功能是:

    当点击开始按钮,则更新进度条,并且在输入框内把完成的百分比输出(这里只做例子,没有真正去做某个工作)。

     1 package test;
     2 
     3 import java.awt.FlowLayout;
     4 import java.awt.event.ActionEvent;
     5 import java.awt.event.ActionListener;
     6 
     7 import javax.swing.JButton;
     8 import javax.swing.JFrame;
     9 import javax.swing.JProgressBar;
    10 import javax.swing.JTextField;
    11 
    12 public class SwingThreadTest1 extends JFrame {
    13     private static final long serialVersionUID = 1L;
    14     private static final String STR = "Completed : ";
    15     private JProgressBar progressBar = new JProgressBar();
    16     private JTextField text = new JTextField(10);
    17     private JButton start = new JButton("Start");
    18     private JButton end = new JButton("End");
    19     private boolean flag = false;
    20     private int count = 0;
    21 
    22     public SwingThreadTest1() {
    23         this.setLayout(new FlowLayout());
    24         add(progressBar);
    25         text.setEditable(false);
    26         add(text);
    27         add(start);
    28         add(end);
    29         start.addActionListener(new Start());
    30         end.addActionListener(new End());
    31     }
    32 
    33     private void go() {
    34         while (count < 100) {
    35             try {
    36                 Thread.sleep(100);// 这里比作要完成的某个耗时的工作
    37             } catch (InterruptedException e) {
    38                 e.printStackTrace();
    39             }
    40             // 更新进度条和输入框
    41             if (flag) {
    42                 count++;
    43                 progressBar.setValue(count);
    44                 text.setText(STR + String.valueOf(count) + "%");
    45             }
    46         }
    47     }
    48 
    49     private class Start implements ActionListener {
    50         public void actionPerformed(ActionEvent e) {
    51             flag = true;// 设置开始更新的标志
    52             go();// 开始工作
    53             System.out.println(Thread.currentThread().getName());
    54         }
    55     }
    56 
    57     private class End implements ActionListener {
    58         public void actionPerformed(ActionEvent e) {
    59             flag = false;// 停止
    60         }
    61     }
    62 
    63     public static void main(String[] args) {
    64         SwingThreadTest1 fg = new SwingThreadTest1();
    65         fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    66         fg.setSize(300, 100);
    67         fg.setVisible(true);
    68     }
    69 }
    View Code

    运行代码发现,

    现象1当点击了开始按钮,画面就卡住了。按钮不能点击,进度条没有被更新,输入框上也没有任何信息。

    原因分析:Swing是线程不安全的,是单线程的设计,所以只能从事件派发线程访问将要在屏幕上绘制的Swing组件。ActionListener的actionPerformed方法是在事件派发线程中调用执行的,而点击了开始按钮后,执行了go()方法,在go()里,虽然也去执行了更新组件的方法

    progressBar.setValue(count);

    text.setText(STR + String.valueOf(count) + "%");

    但由于go()方法直到循环结束,它并没有返回,所以更新组件的操作一直没有被执行,这就造成了画面卡住的现象。

    go方法也一直在事件派发进程上,和更新UI在一个进程中,在一个进程中就必然是顺序执行的了。

    现象2过了一段时间(go方法里的循环结束了)后,画面又可以操作,并且进度条被更新,输入框也出现了我们想看到的信息。

    原因分析:通过在现象1的分析,很容易联想到,当go()方法返回了,则其他的线程(更新组件)可以被派发了,所以画面上的组件被更新了。

     为了让画面不会卡住,我们来修改代码,将耗时的工作放在一个线程里去做。

     1 package test;
     2 
     3 import java.awt.FlowLayout;
     4 import java.awt.event.ActionEvent;
     5 import java.awt.event.ActionListener;
     6 
     7 import javax.swing.JButton;
     8 import javax.swing.JFrame;
     9 import javax.swing.JProgressBar;
    10 import javax.swing.JTextField;
    11 
    12 public class SwingThreadTest2 extends JFrame {
    13     private static final long serialVersionUID = 1L;
    14     private static final String STR = "Completed : ";
    15     private JProgressBar progressBar = new JProgressBar();
    16     private JTextField text = new JTextField(10);
    17     private JButton start = new JButton("Start");
    18     private JButton end = new JButton("End");
    19     private boolean flag = false;
    20     private int count = 0;
    21 
    22     GoThread t = null;
    23 
    24     public SwingThreadTest2() {
    25         this.setLayout(new FlowLayout());
    26         add(progressBar);
    27         text.setEditable(false);
    28         add(text);
    29         add(start);
    30         add(end);
    31         start.addActionListener(new Start());
    32         end.addActionListener(new End());
    33     }
    34 
    35     private void go() {
    36         while (count < 100) {
    37             try {
    38                 Thread.sleep(10);
    39             } catch (InterruptedException e) {
    40                 e.printStackTrace();
    41             }
    42             if (flag) {
    43                 count++;
    44                 System.out.println(count);
    45                 progressBar.setValue(count);
    46                 System.out.println(Thread.currentThread().getName());
    47                 text.setText(STR + String.valueOf(count) + "%");
    48             }
    49         }
    50     }
    51 
    52     private class Start implements ActionListener {
    53         public void actionPerformed(ActionEvent e) {
    54             System.out.println(Thread.currentThread().getName());
    55             flag = true;
    56             if (t == null) {
    57                 t = new GoThread();
    58                 t.start();
    59             }
    60         }
    61     }
    62 
    63     // 执行复杂工作,然后更新组件的线程
    64     class GoThread extends Thread {
    65         public void run() {
    66             // do something...
    67             go();
    68         }
    69     }
    70 
    71     private class End implements ActionListener {
    72         public void actionPerformed(ActionEvent e) {
    73             flag = false;
    74         }
    75     }
    76 
    77     public static void main(String[] args) {
    78         SwingThreadTest2 fg = new SwingThreadTest2();
    79         fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    80         fg.setSize(300, 100);
    81         fg.setVisible(true);
    82     }
    83 }
    View Code

    我们执行了程序,结果和我们想要的一样,画面不会卡住了。

    那这个程序是否没有问题了呢?

    我们自定义了一个线程GoThread,在这里我们完成了那些耗时的工作,可以看作是“工作线程”,

    而对于组件的更新,我们也放在了“工作线程”里完成了。

    在这里,在事件派发线程以外的线程里设置进度条,是一个危险的操作,运行是不正常的。(对于输入框组件的更新是安全的。)

    只有从事件派发线程才能更新组件,根据这个原则,我们来修改我们现有代码。

     1 package test;
     2 
     3 import java.awt.FlowLayout;
     4 import java.awt.event.ActionEvent;
     5 import java.awt.event.ActionListener;
     6 
     7 import javax.swing.JButton;
     8 import javax.swing.JFrame;
     9 import javax.swing.JProgressBar;
    10 import javax.swing.JTextField;
    11 import javax.swing.SwingUtilities;
    12 
    13 public class SwingThreadTest3 extends JFrame {
    14     private static final long serialVersionUID = 1L;
    15     private static final String STR = "Completed : ";
    16     private JProgressBar progressBar = new JProgressBar();
    17     private JTextField text = new JTextField(10);
    18     private JButton start = new JButton("Start");
    19     private JButton end = new JButton("End");
    20     private boolean flag = false;
    21     private int count = 0;
    22 
    23     private GoThread t = null;
    24 
    25     private Runnable run = null;// 更新组件的线程
    26 
    27     public SwingThreadTest3() {
    28         this.setLayout(new FlowLayout());
    29         add(progressBar);
    30         text.setEditable(false);
    31         add(text);
    32         add(start);
    33         add(end);
    34         start.addActionListener(new Start());
    35         end.addActionListener(new End());
    36 
    37         run = new Runnable() {// 实例化更新组件的线程
    38             public void run() {
    39                 System.out.println(Thread.currentThread().getName());
    40                 progressBar.setValue(count);
    41                 text.setText(STR + String.valueOf(count) + "%");
    42             }
    43         };
    44     }
    45 
    46     private void go() {
    47         while (count < 100) {
    48             try {
    49                 Thread.sleep(10);
    50             } catch (InterruptedException e) {
    51                 e.printStackTrace();
    52             }
    53             if (flag) {
    54                 count++;
    55                 System.out.println(Thread.currentThread().getName());
    56                 SwingUtilities.invokeLater(run);// 将对象排到事件派发线程的队列中
    57             }
    58         }
    59     }
    60 
    61     private class Start implements ActionListener {
    62         public void actionPerformed(ActionEvent e) {
    63             flag = true;
    64             if (t == null) {
    65                 t = new GoThread();
    66                 t.start();
    67             }
    68         }
    69     }
    70 
    71     class GoThread extends Thread {
    72         public void run() {
    73             // do something...
    74             go();
    75         }
    76     }
    77 
    78     private class End implements ActionListener {
    79         public void actionPerformed(ActionEvent e) {
    80             flag = false;
    81         }
    82     }
    83 
    84     public static void main(String[] args) {
    85         SwingThreadTest3 fg = new SwingThreadTest3();
    86         fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    87         fg.setSize(300, 100);
    88         fg.setVisible(true);
    89     }
    90 }
    View Code

    解释:SwingUtilities.invokeLater()方法使事件派发线程上的可运行对象排队。当可运行对象排在事件派发队列的队首时,就调用其run方法。其效果是允许事件派发线程调用另一个线程中的任意一个代码块。

    还有一个方法SwingUtilities.invokeAndWait()方法,它也可以使事件派发线程上的可运行对象排队。

    区别:

    下面这个是弹出个alert窗口。若用invokeAndWait(),那么打印一段文字将在你点击了OK buton之后才会执行,而如用invokeLater()则,立马后执行输出操作。

     1 package test;
     2 
     3 import java.lang.reflect.InvocationTargetException;
     4 
     5 import javax.swing.JOptionPane;
     6 import javax.swing.SwingUtilities;
     7 
     8 public class Invoke  {
     9     public static void main(String[] args) {
    10         Runnable showModalDialog = new Runnable() {
    11             public void run() {
    12                  JOptionPane.showMessageDialog(null, "No active shares found on this IP!");
    13             }
    14         };
    15         try {
    16             SwingUtilities.invokeAndWait(showModalDialog);
    17             System.out.println("sssssssssss");
    18         } catch (InterruptedException e) {
    19             // TODO Auto-generated catch block
    20             e.printStackTrace();
    21         } catch (InvocationTargetException e) {
    22             // TODO Auto-generated catch block
    23             e.printStackTrace();
    24         }
    25     }
    26 }
    View Code
  • 相关阅读:
    Apriori 算法-如何进行关联规则挖掘
    Nginx 常用命令
    Nginx Location匹配规则
    Nginx 负载均衡
    angular 路由传参的三种方式
    JAVA中final关键字的作用
    Python函数参数和注解是什么
    JMeter测试计划配置项解析
    JMeter元件作用域实践指南
    原来Python函数只是个对象
  • 原文地址:https://www.cnblogs.com/fengjian/p/3476919.html
Copyright © 2011-2022 走看看