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

背景概念

同步与异步:

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

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

串行与并行:

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

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

队列:

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

Swing程序中的线程

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

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

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

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

EDT线程注意事项

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

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

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

二、在非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请求添加到事件队列中后,会一直阻塞,直到该请求被完成后才会返回。

三、耗时操作应放到任务线程中,通过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();
}
});
}
}

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

四、千万别在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线程机制的注意事项有:

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

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

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

4、耗时操作由任务线程执行,通过SwingWorker类显示启动任务线程。

over。

 

swing线程机制的更多相关文章

  1. 从swing分发线程机制上理解多线程[转载]

    本文参考了 http://space.itpub.net/13685345/viewspace-374940,原文作者:javagui 在多线程编程当中,总会提到图形编程,比如java中的swing, ...

  2. 恶补Java Swing线程刷新UI机制(由浅到深的参考大佬博文)

    1. java中进度条不能更新问题的研究 感谢大佬:https://blog.csdn.net/smartcat86/article/details/2226681 为什么进度条在事件处理过程中不更新 ...

  3. 一步一步掌握java的线程机制(一)----创建线程

    现在将1年前写的有关线程的文章再重新看了一遍,发现过去的自己还是照本宣科,毕竟是刚学java的人,就想将java的精髓之一---线程进制掌握到手,还是有点难度.等到自己已经是编程一年级生了,还是无法将 ...

  4. Vue.js线程机制问题还是数据双向绑定有延迟的问题

    最近用select2做一个下拉多选,若只是从后端获取一个列表渲染还好说,没有任何问题.但要用select2对数据初始化时进行selected的默认选项进行显示,就出现问题了. vm.$set('are ...

  5. WebClient.DownloadFile(线程机制,异步下载文件)

    线程机制(避免卡屏),异步下载文件. 我做网站的监控,WebClient.DownloadFile这个方法是我经常用到的,必要的时候肯定是要从网上下载些什么(WebRequest 也可以下载网络文件, ...

  6. Java多线程与并发库高级应用-传统线程机制回顾

    1.传统线程机制的回顾 1.1创建线程的两种传统方式 在Thread子类覆盖的run方法中编写运行代码 // 1.使用子类,把代码放到子类的run()中运行 Thread thread = new T ...

  7. 从setTimeout到浏览器线程机制

    看高性能javascipt 这本书时,看到这么一句话: Putting scripts at the top of the page in this way typically leads to a ...

  8. java中线程机制

    java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...

  9. 线程机制、CLR线程池以及应用程序域

    最近在总结多线程.CLR线程池以及TPL编程实践,重读一遍CLR via C#,比刚上班的时候收获还是很大的.还得要多读书,读好书,同时要多总结,多实践,把技术研究透,使用好. 话不多说,直接上博文吧 ...

随机推荐

  1. Xcode编译报错信息总结

    1.dyld: Library not loaded: @rpath/.../xxx.framework 一般与这个库的加载路径有关,先看看是否将静态库设置成了动态库(库的Mach-o Type选项) ...

  2. Java50道经典习题-程序37 报数

    题目:有n个人围成一圈,顺序排号.从第一个人开始报数(从1到3报数),凡报到3的人退出圈子,问最后留下的是原来第几号的那位.分析:最后留下的是第n号那位 import java.util.Scanne ...

  3. javascript 实现类似百度联想输入,自动补全功能

    js  实现类似百度联想输入,自动补全功能 方案一: search是搜索框id="search" //点击页面隐藏自动补全提示框 document.onclick = functi ...

  4. javascript 字典类型的使用

    javascript  字典类型的使用 1.使用Array: var arr = new Array(); arr["zs"] = "zhangsan"; ar ...

  5. 老男孩Day6作业:计算器

    作业需求: 1.实现加减乘除及拓号优先级解析 2.用户输入 1 - 2 * ( (60-30 +(-40/5) * (9-2*5/3 + 7 /3*99/4*2998 +10 * 568/14 )) ...

  6. Qt 学习之路 2(17):文件对话框

    Home / Qt 学习之路 2 / Qt 学习之路 2(17):文件对话框 Qt 学习之路 2(17):文件对话框  豆子  2012年9月24日  Qt 学习之路 2  85条评论 在前面的章节中 ...

  7. Windows中报错:Fatal error in launcher: Unable to create process using '"' 的解决方案

    Windows 下同时存在 Python2 和 Python3 使用 pip 时系统报错:Fatal error in launcher: Unable to create process using ...

  8. git 日常使用从入门到真香

    目录 git 日常使用从入门到真香 一.Git简介 二.Git常用命令 三.git操作流程 四.报错处理 git 日常使用从入门到真香 一.Git简介 Git是一个开源的分布式版本控制系统,可以有效. ...

  9. C++_了解虚函数的概念

    第一.先了解基本概念介绍: 虚函数.多态.继承都是紧密相关的概念.而继承是所有概念的基础: 继承的概念:是面向对象编程的三大特性之一(另外两个是:多态和封装):继承可以使得子类具有父类的属性和方法或者 ...

  10. Luogu P5201 [USACO19JAN]Shortcut 最短路树???

    最短路树...开眼界了...之前想也没想过.... 先跑出来1到每个点最短路,然后建树时要标记点的入度,否则会多连边...然后深搜时更新新答案就是 #include<cstdio> #in ...