swing线程机制

在介绍swing线程机制以前,先介绍一些背景概念。java

背景概念数据库

同步与异步小程序

    同步是指程序在发起请求后开始处理事件并等待处理的结果或等待请求执行完毕,在此以前程序被阻塞(block)直到请求完成。安全

    异步是当前程序发起请求后当即返回,当前程序不会当即处理该事件并等待处理的结果,请求是在稍后的某一时间才被处理。数据结构

串行与并行并发

    串行是指多个要处理的请求按照顺序执行,处理完一个再处理下一个。app

    并行能够理解为并发,指的是同时处理多个请求(实际上咱们只能理论上这么理解,特别是CPU数目少于线程数的机器而言,真正意义的并发是不存在的,各个线程只是断断续续地交替执行(以消耗CPU时间片的形式))。异步

队列ide

 队列是一种线性的数据结构,元素遵照“先进先出”原则。队列的处理方式是处理完一个再处理下一个。测试

 

Swing程序中的线程

 一个swing程序包含三种类型的线程:初始化线程(Initial Thread)、事件分派线程(Event Dispatch Thread)和任务线程(Worker Thread)。

 初始化线程:每一个程序都有一个main方法,这是程序执行的入口,该方法运行在初始化线程上。初始化线程读取程序参数并初始化一些对象。在许多Swing程序中,该线程主要目的是启动程序的图形用户界面(GUI)。一旦GUI启动后,对于大多数事件驱动的桌面程序来讲,初始化线程的工做就结束了,程序的控制权就交给了UI。

 事件分派线程(EDT ) :主要负责GUI组件的绘制和更新,并响应用户的输入。每一个EDT都会负责管理一个事件队列(EventQueue),用户每次对界面更新的请求(包括键盘、鼠标等事件)都会排到事件队列中,而后等待EDT的处理。

 任务线程:主要负责执行和界面无直接关系的耗时任务和输入/输出密集型操做,即任何干扰或延迟UI事件的处理都应该由任务线程来完成。注意,任务线程是经过javax.swing.SwingWorker类显式启动的。

 

EDT线程注意事项

1、任何GUI的请求都必须由EDT线程来处理

 EDT线程将全部的GUI组件绘制和更新请求以及事件请求都放入了一个事件队列中。队列这种数据结构前面也讲过了,它是线性、“先进先出”的。因此,经过事件队列的机制,就能够将并发的GUI请求转化为事件队列,从而按顺序处理。这样就能够保证线程安全。因此说,尽管大多数swing API自己不是线程安全的,可是swing经过EDT线程和事件队列机制实现了保障线程安全。

 同理,不建议从其余线程直接访问UI组件及其事件处理器,这会破坏线程安全的保障,可能会致使界面更新和绘制错误

2、在非EDT线程中经过invokeLater和invokeAndWait方法向EDT线程的事件队列添加GUI请求

 有的时候须要在一个非EDT线程中调用swing API来处理GUI请求,根据第一条注意事项,显然咱们不能直接访问GUI组件。这时候咱们能够调用这两个方法将GUI请求添加到EDT线程的事件队列中。

 举个例子:咱们有一个类A继承了JFrame,如何在main方法中正确的启动GUI呢?咱们知道main方法属于初始化线程,这就是典型的非EDT线程访问GUI组件的问题。

 错误的启动方式为: new A();

 若是这么作了,就至关于在非EDT线程中直接访问了GUI组件,这破坏了线程安全。

 正确的启动方式为:

  SwingUtilities.invokeLater(new Runnable() {
     public void run() {
      createGUI();
     }
   });

  经过invokeLater和invoke方法,能够从一个非EDT线程中,将GUI请求添加到EDT线程的事件队列中去。

  这两个方法的区别是:invokeLater是异步的,调用该方法时,该方法将GUI请求添加到事件队列中后直接返回。InvokeAndWait是同步的,调用该方法时,该方法将GUI请求添加到事件队列中后,会一直阻塞,直到该请求被完成后才会返回。

3、耗时操做应放到任务线程中,经过SwingWorker启动任务线程

 EDT的事件队列的机制在保障了线程安全的同时,也引入了一个新的问题:假设事件队列中某一个GUI请求执行时间很是长,那么因为队列的特色,队列中的后续GUI请求都会被阻塞。在实际的应用程序中,表现为:点击了一个按钮触发了耗时任务后其余的组件都失去响应,必须等待该任务完成界面才能恢复响应。

咱们用一个简单的程序测试一下,一个简单的swing小程序。start按钮模拟写入数据(耗时操做),display按钮用于将文本框中的内容输出到文本显示区中。数据写入完成后,在文本框中输出“数据写入完毕”。在点击start按钮后,我连续点击了三次display按钮都没有任何响应。三秒钟以后,响应结果出来了,文本显示区中输出了三行“数据写入完毕”。用户体验极差。

代码以下:

package swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestEDT {
    public static void createGUI() {
        JFrame frame=new JFrame("swing线程机制");
        JTextField tf=new JTextField("hello world");
        JTextArea ta=new JTextArea();
        ta.setEditable(false);
ta.setPreferredSize(new Dimension(400,300)); JButton b1
=new JButton("start"); JButton b2=new JButton("display"); JPanel p1=new JPanel(); JPanel p2=new JPanel(); p1.setLayout(new BorderLayout()); p2.add(b1); p2.add(b2); p1.add(ta,BorderLayout.NORTH); p1.add(tf,BorderLayout.CENTER); p1.add(p2,BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(p1);
     frame.pack(); frame.setVisible(
true); //start按钮开始写入数据,该操做耗时好久 b1.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { //用线程休眠方法模拟耗时的写入数据操做 try { Thread.sleep(3000); } catch (InterruptedException e1) { e1.printStackTrace(); } tf.setText("数据写入完毕"); } }); //display按钮用于将文本框中的信息输出到文本显示区中 b2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ta.append(tf.getText()+"\n"); } }); } //用正确的方式启动GUI public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createGUI(); } }); } }

考虑到用户体验性,应使用独立的任务线程来执行耗时计算或输入输出密集型任务,好比同数据库通讯、访问网站资源、读写大数据量的文件等操做。

4、千万别在EDT线程中调用invokeAndWait方法

 在非EDT线程中,调用invokeAndWait方法能够很好地将GUI请求添加到EDT线程的事件队列中。可是若是在EDT线程中调用该方法会发生死锁。

 这是由于若是在EDT线程中调用invokeAndWait方法,GUI请求被添加到了事件队列中后,invokeAndWait方法根据其特性,会一直等待直到EDT线程执行完本身run方法中的请求为止。可是对于队列而言,默认请求是被添加到尾部的。EDT线程根据到达不了该请求的位置,由于它如今的请求也就是invokeAndWait尚未执行完。

 简而言之,就是:EDT线程必须完成该方法后才能去完成该GUI请求,可是必须先完成该GUI请求才能完成该方法。这样双方互相等待,产生了死锁。

任务线程的用法

任务线程是须要显示地经过SwingWorker类调用的。

泛型参数<T,V>的分别表明:T 是 此 SwingWorkerdoInBackgroundget 方法返回的结果类型;V 是用于保存此 SwingWorkerpublishprocess 方法的中间结果的类型。

顾名思义,该类的doInBackground()方法表示在后台执行的方法,由任务线程完成,用于执行耗时操做的代码;done()方法是doInBackground方法执行完成后再调用的方法,方法体中的内容交付给EDT线程,用于处理GUI请求。

仍然以以前的模拟“写入数据”程序为例,演示任务线程的用法。

代码以下:

package swing;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class TestEDT {
    public static void createGUI() {
        JFrame frame=new JFrame("swing线程机制");
        JTextField tf=new JTextField("hello world");
        JTextArea ta=new JTextArea();
        ta.setEditable(false);
ta.setPreferredSize(new Dimension(400,300)); JButton b1
=new JButton("start"); JButton b2=new JButton("display"); JPanel p1=new JPanel(); JPanel p2=new JPanel(); p1.setLayout(new BorderLayout()); p2.add(b1); p2.add(b2); p1.add(ta,BorderLayout.NORTH); p1.add(tf,BorderLayout.CENTER); p1.add(p2,BorderLayout.SOUTH); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.getContentPane().add(p1); frame.pack(); frame.setVisible(true); //start按钮开始写入数据,该操做耗时好久 b1.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { new SwingWorker<Integer,Void>(){ protected Integer doInBackground() { //模拟写入数据这一耗时操做 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } return 1; } protected void done() { tf.setText("数据写入完毕"); } }.execute(); } }); //display按钮用于将文本框中的信息输出到文本显示区中 b2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { ta.append(tf.getText()+"\n"); } }); } //用正确的方式启动GUI public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createGUI(); } }); } }

测试结果为:点击start按钮后,其余的组件仍然有良好的响应。三秒钟以后,文本框内容成功显示为“数据写入完毕”。

总结一下,swing线程机制的注意事项有:

一、全部的GUI请求必须都由EDT线程完成(保障线程安全),不建议经过非EDT线程访问GUI组件

二、非EDT线程经过invokeLater和invokeAndWait方法将GUI请求交付给EDT线程。

三、禁止在EDT线程中调用invokeAndWait方法(形成死锁)。

四、耗时操做由任务线程执行,经过SwingWorker类显示启动任务线程。

over。

相关文章
相关标签/搜索