swing线程机制
在介绍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 是 此 SwingWorker 的 doInBackground 和 get 方法返回的结果类型;V 是用于保存此 SwingWorker 的 publish 和 process 方法的中间结果的类型。
顾名思义,该类的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线程机制的更多相关文章
- 从swing分发线程机制上理解多线程[转载]
本文参考了 http://space.itpub.net/13685345/viewspace-374940,原文作者:javagui 在多线程编程当中,总会提到图形编程,比如java中的swing, ...
- 恶补Java Swing线程刷新UI机制(由浅到深的参考大佬博文)
1. java中进度条不能更新问题的研究 感谢大佬:https://blog.csdn.net/smartcat86/article/details/2226681 为什么进度条在事件处理过程中不更新 ...
- 一步一步掌握java的线程机制(一)----创建线程
现在将1年前写的有关线程的文章再重新看了一遍,发现过去的自己还是照本宣科,毕竟是刚学java的人,就想将java的精髓之一---线程进制掌握到手,还是有点难度.等到自己已经是编程一年级生了,还是无法将 ...
- Vue.js线程机制问题还是数据双向绑定有延迟的问题
最近用select2做一个下拉多选,若只是从后端获取一个列表渲染还好说,没有任何问题.但要用select2对数据初始化时进行selected的默认选项进行显示,就出现问题了. vm.$set('are ...
- WebClient.DownloadFile(线程机制,异步下载文件)
线程机制(避免卡屏),异步下载文件. 我做网站的监控,WebClient.DownloadFile这个方法是我经常用到的,必要的时候肯定是要从网上下载些什么(WebRequest 也可以下载网络文件, ...
- Java多线程与并发库高级应用-传统线程机制回顾
1.传统线程机制的回顾 1.1创建线程的两种传统方式 在Thread子类覆盖的run方法中编写运行代码 // 1.使用子类,把代码放到子类的run()中运行 Thread thread = new T ...
- 从setTimeout到浏览器线程机制
看高性能javascipt 这本书时,看到这么一句话: Putting scripts at the top of the page in this way typically leads to a ...
- java中线程机制
java中线程机制,一开始我们都用的单线程.现在接触到多线程了. 多线性首先要解决的问题是:创建线程,怎么创建线程的问题: 1.线程的创建: 四种常用的实现方法 1.继承Thread. Thread是 ...
- 线程机制、CLR线程池以及应用程序域
最近在总结多线程.CLR线程池以及TPL编程实践,重读一遍CLR via C#,比刚上班的时候收获还是很大的.还得要多读书,读好书,同时要多总结,多实践,把技术研究透,使用好. 话不多说,直接上博文吧 ...
随机推荐
- 国外物联网平台(2):微软Azure IoT
国外物联网平台(2)——微软Azure IoT 马智 平台定位 连接设备.其它 M2M 资产和人员,以便在业务和操作中更好地利用数据. 连接 IoT 设备 将所有设备连接到云,从这些设备接收大规模数据 ...
- 转:IDEA 与 eclipse 的部分区别!
Idea 与 Eclipse 快捷键的区别,上为Eclipse的快捷键,下为Idea的快捷键查找类名CTRL + SHIFT + RCTRL + N 查找JAR包中的类CTRL + SHIFT + T ...
- [python]glob模块中的glob()函数为什么返回空列表??
最近在学习语音的知识,看一个语音合成实现的相关工具包的源代码,碰到了glob()函数.然后开启了我与这个函数相爱想杀的一个下午. 摘自官网解释: https://docs.python.org/2/l ...
- ubuntu - 14.04,安装Eclipse(开源开发工具)
一,安装JDK:Eclipse必须有JDK才能运行,所以首先我们确定系统是否已经安装了JDK,我们在shell里面输入:“java -version”,如果已经安装了,就会打印出来当前JDK版本信息, ...
- RGB颜色表-网址不见了看这里
英文不翻译 翻译成中文
- STL迭代器思考
在遍历STL容器类对象时进行插入和删除操作本身即是不合理的...再想想,待续... STL容器迭代过程中删除元素技巧(转) 1.连续内存序列容器(vector,string,deque) 序列容器 ...
- JavaScript之DOM实践
前言 HTML的DOM是JavaScript有能力对HTML作出反应,有时候,我们需要一些网页效果,为了更好地适应用户的效果,比如,我们平时接触,点击某个按钮,按下去的瞬间会变色,再比如,有时候鼠标经 ...
- 咕咕(数位dp+AC自动机)
咕咕(数位dp+AC自动机) 若一个字符串的字符集合是0~m-1,那么称它为m进制字符串.给出n个m进制字符串\(s_i\),每个字符串的权值为\(v_i\).对于另一个m进制字符串\(S\),设\( ...
- linux模式切换,进程切换
内核态和用户态的切换: 用户态到内核态的转换:1.进行系统调用,2.异步中断,3.外部硬件中断 检查特权级别的变化:当异常发生在用户态,而异常处理函数则必须运行在内核态,则此时必须调用内核态的堆栈(系 ...
- SP2713 GSS4 - Can you answer these queries IV
题目大意 \(n\) 个数,和在\(10^{18}\)范围内. 也就是\(\sum~a_i~\leq~10^{18}\) 现在有两种操作 0 x y 把区间[x,y]内的每个数开方,下取整 1 x y ...