Swing:关于Java界面编程的第一课,如何正确的处理界面中的线程

转载:http://blog.csdn.net/sunyiz/article/details/8004573

关于 Java 的 GUI ,关于 Swing,其实有一件事情是非常重要的
那就是线程!
如何正确的使用 GUI 线程,
什么样的代码应该在 GUI 线程上执行?
什么样的代码应该用其他线程执行?
其实这些都很重要,
但是,很多关于 Java 的教材都没有强调过这些,甚至有的书完全就忽略了这一点
所以,我在这里要给所以开始接触 Swing 的人,讲述一个重要概念:
Swing 中的线程

---------------------------------------------------------------------------

要说明这个问题,我们要先来看一个贴子:
http://tieba.baidu.com/p/1872190099
这是我昨天发的贴子,里面已经简单叙述了 GUI 中的各个线程

现在,我们先来回顾一下 EDT----这个至关重要的线程
这个 EDT 是干什么的呢?它负责指派所有的 GUI 事件
比如键盘按钮按下后,派发给对应控件的监听器
比如鼠标点击后,派发给对应控件的监听器
比如:绘制控件
所以,我们一般又喜欢把 EDT 叫做 GUI 线程。

EDT 是一种排队的模式,就是各种事件会在其中排队等待,依次执行
其实所有的绘制,在 Swing 内部处理时,全都包装成了 Paint 事件然后进入 EDT 排队
而且 EDT 还会智能的合并多个连续的 Paint 事件,把它们包装成一个 Paint 事件

下面阐述一个重要的规范:Swing 单线程规范
“所有的界面相关更新,都应当在 EDT 中执行”
这个规范非常重要,在你编写 Swing 程序的过程中,请一直牢记他

---------------------------------------------------------------------------

现在,让我们先来设定一个小小的目标,然后我们去实现它,从中探讨和发现问题
就做一个按时间变化的进度条吧,很多新手在刚开始时,都对这个功能的实现表示纠结
下面,我们先来写一段代码:

[java] view plain copy

  1. public class ProgressFrame extends JFrame implements ActionListener {
  2. private JButton btn = new JButton("Start");
  3. private JProgressBar bar = new JProgressBar(){
  4. public void paint(Graphics g) {
  5. super.paint(g);
  6. System.out.println("paint");
  7. }
  8. };
  9. public ProgressFrame() {
  10. init();
  11. }
  12. private void init() {
  13. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  14. setSize(300,200);
  15. setLocationRelativeTo(null);
  16. setLayout(new FlowLayout());
  17. add(btn);
  18. add(bar);
  19. btn.addActionListener(this);
  20. setVisible(true);
  21. }
  22. @Override
  23. public void actionPerformed(ActionEvent e) {
  24. for (int i = 0; i <= 10; i++) {
  25. bar.setValue(i * 10);
  26. }
  27. }
  28. public static void main(String[] args) {
  29. new ProgressFrame();
  30. }
  31. }

我们的想法很简单,就是在点下按钮后,让进度条设值 11 次,达到动画效果
但是你运行后,点下按钮时,发现几乎是瞬间,进度条就满了,
“paint”也只打印了一次

---------------------------------------------------------------------------

是不是没有延时太快了?
那让我们对 actionPerformed 方法做一点小小的修改:

[java] view plain copy

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. for (int i = 0; i <= 10; i++) {
  4. try {
  5. Thread.sleep(200);
  6. } catch (InterruptedException e1) {
  7. e1.printStackTrace();
  8. }
  9. bar.setValue(i * 10);
  10. }
  11. }

你会发现,按下按钮后,界面如死机般僵硬 2 秒之后,依然是进度条直接满
“paint”也只打印了一次

这是什么原因呢?

下面我们要再说一个在编写 Swing 程序中,应该遵守的规则:

“不要在 EDT 中执行耗时代码,耗时工作应当有一个单独的线程去处理”

因为如果你让耗时代码占用了 EDT,那 EDT 中的那些绘制啊什么的任务都将没空执行
这些事件被压到 EDT 的最后去排队,然后又被 EDT 合并成了一个 Paint 事件
从而结果就是:Paint 只在最后不再 sleep 之后,执行了一次

也许有人会很奇怪,这个 actionPerformed 怎么就在 EDT 中执行了呢?
还记得我们说过的 EDT 的作用么?
它会接受 toolkit 线程传递来的系统事件,
然后传递给对应控件的对应监听器,执行对应的方法
所以其实,这个 actionPerformed ,是由 EDT 调用执行的
其实 Swing 控件的大部分监听器的各种方法,都是在 EDT 上执行的
所以,我们要避免在这些监听器的方法中执行耗时操作,否则界面就会卡死!

---------------------------------------------------------------------------

那我们要如何修改才能实现这样的效果呢?
我们需要让这个耗时的工作,在 EDT 之外的线程执行才行:
再次修改 actionPerformed 方法:

[java] view plain copy

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. Runnable runnable = new Runnable() {
  4. @Override
  5. public void run() {
  6. for (int i = 0; i <= 10; i++) {
  7. try {
  8. Thread.sleep(200);
  9. } catch (InterruptedException e1) {
  10. e1.printStackTrace();
  11. }
  12. bar.setValue(i * 10);
  13. }
  14. }
  15. };
  16. new Thread(runnable).start();
  17. }

我们用了一个单独的线程来执行这段代码,

现在让我们再次执行,终于,我们看到了我们期望中的效果,进度条慢慢增加
“paint”也打印了十一次,那我们的程序是否就 OK 了呢?
答案是否定的,因为我们在这样实现的同时,又破坏了 Swing 的单线程规范
没有在 EDT 中执行界面更新操作,
bar.setValue(i * 10);
这句话,应该在 EDT 中执行。
那这里不就有个矛盾了么?在 EDT 中执行也不行,不在 EDT 中执行也不行……

---------------------------------------------------------------------------

其实我们要的只是 bar.setValue(i * 10); 这一句话在 EDT 中执行而已
而 Swing 提供了一个强大的工具类:SwingUtilities
它提供了好几个功能强大的方法,这里,我们需要的是:invokeLater 这个方法
这个方法的作用是:把一个任务,排队到 EDT 的最后,等待执行,
我们现在再次修改 actionPerformed 方法:

[java] view plain copy

  1. @Override
  2. public void actionPerformed(ActionEvent e) {
  3. Runnable runnable = new Runnable() {
  4. int i = 0;
  5. @Override
  6. public void run() {
  7. for (i = 0; i <= 10; i++) {
  8. try {
  9. Thread.sleep(200);
  10. } catch (InterruptedException e1) {
  11. e1.printStackTrace();
  12. }
  13. SwingUtilities.invokeLater(new Runnable() {
  14. @Override
  15. public void run() {
  16. bar.setValue(i * 10);
  17. }
  18. });
  19. }
  20. }
  21. };
  22. new Thread(runnable).start();
  23. }

再次执行,效果和上一次一样,也满足了规范,皆大欢喜。
其实 Swing 为了处理这种类似的问题,专门提供了一个功能强大的类:SwingWorker
关于这个 SwingWorker 我会在以后找时间进行详细的解释,今天先说这么多吧。

---------------------------------------------------------------------------

当然,其实我们的程序还有一个小小的瑕疵:

[java] view plain copy

  1. public static void main(String[] args) {
  2. new ProgressFrame();
  3. }

这里,new ProgressFrame(); 的过程,其实也包含了大量的界面刷新等等
我们不应该让这样的代码在主线程中执行,应该把它放到 EDT 中去:
这样修改一下:

[java] view plain copy

  1. public static void main(String[] args) {
  2. SwingUtilities.invokeLater(new Runnable() {
  3. @Override
  4. public void run() {
  5. new ProgressFrame();
  6. }
  7. });
  8. }

---------------------------------------------------------------------------

这就是关于 Swing 的最重要的第一课:两个规范
“所有的界面相关更新,都应当在 EDT 中执行”
“不要在 EDT 中执行耗时代码,耗时工作应当有一个单独的线程去处理”

这两条规范,将会伴随你的 Swing 程序,直到永远

时间: 2024-10-10 13:03:57

Swing:关于Java界面编程的第一课,如何正确的处理界面中的线程的相关文章

为什么qt成为c++界面编程的第一选择

一.前言 为什么现在QT越来越成为界面编程的第一选择,笔者从事qt界面编程已经有接近8年,在这之前我做C++界面都是基于MFC,也做过5年左右.当时为什么会从MFC转到QT,主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MFC的界面库代码也是比较混乱,当时主要看到qt有qss样式设计这个功能,决定试下.当项目移植到到QT后,觉得MFC真的是可以抛弃了,MFC相对于QT缺点实在太多.当然MFC还有一个致命的缺陷,没法跨平台. 当然有同学可能说为什么不直接使用C# WPF做界面,我们

java工程开发之图形化界面之(第一课)

下面我们先上代码: 1 package 一个事例图形小应用程序; 2 import javax.swing.JApplet; 3 import java.awt.Graphics; 4 5 public class 绘制图形 extends JApplet{ 6 public void paint(Graphics canvas){ 7 canvas.drawOval(100,50,200,200); 8 canvas.fillOval(155, 100, 10, 20); 9 canvas.f

VC++编程之第一课笔记

第一课 Windows程序内部运行原理 API 操作系统把它所能够完成的功能以函数的形式提供给应用程序使用,应用程序对这些函数的调用就叫做系统调用.这些函数的集合就是Windows操作系统提供给应用程序编程的接口(Application Programming Interface),简称Windows API. 如Create Window就是一个API函数,应用程序调用这个函数,操作系统就会按照该函数提供的参数信息产生一个相应的窗口. MSG(消息结构体) 结构体定义如下: typedef s

转: 【Java并发编程】之十八:第五篇中volatile意外问题的正确分析解答(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17382679 在<Java并发编程学习笔记之五:volatile变量修饰符-意料之外的问题>一文中遗留了一个问题,就是volatile只修饰了missedIt变量,而没修饰value变量,但是在线程读取value的值的时候,也读到的是最新的数据.但是在网上查了很多资料都无果,看来很多人对volatile的规则并不是太清晰,或者说只停留在很表面的层次,一知半解. 这两天看<深入Ja

使用Java理解编程逻辑-第一章

1.什么是Java? Java是前Sun公司(显甲骨文股份有限公司)于1995年推出的高级编程,Java技术应用几乎所有类型和规模的设备上,小到计算机芯片,蜂窝电话,大到超级计算机,无所不在! Java共有三个版本分别为:1.Java SE (平台标准版)Java的核心技术,可开发桌面应用程序 比如微信,QQ.2.Java EE (平台企业版)Java EE是在Java SE的基础上扩展的, 可开发面向Internet的应用程序 比如京东,淘宝3.Java ME 可开发手机上的应用程序 2.配置

java并发编程实战第一章

线程不安全代码测试 private static class UnsafeSequence { private int value; public int getNext() { return value++; } } 使用两个线程分别调用上面的getNext方法1000次,出现了一次线程不安全的情况,在转出的结果中有两个1311: 图片.png 原因分析,与书上说的一致: 图片.png 完整的代码 import java.io.PrintWriter; import java.util.con

Python之路第一课Day9--随堂笔记之二(进程、线程、协程篇)

本节内容 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者消费者模型 Queue队列 开发一个线程池 进程 语法 进程间通讯 进程池 一.进程与线程 1.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 A thread is an ex

JAVA学习第二十八课(多线程(七))- 停止线程和多线程面试题

重点掌握 /* * wait 和 sleep 区别? * 1.wait可以指定时间也可以不指定 * sleep必须指定时间 * 2.在同步中,对CPU的执行权和锁的处理不同 * wait释放执行权,释放锁    sleep释放执行权,不释放锁 */ //同步里具备执行资格的线程不止一个,但是能得到锁的只有一个,所以能执行的也只有一个 一.停止线程的方式 不可能让线程一直在运行,所以需要让线程停止 1.定义循环结束标记 一般而言,线程运行代码都是循环的,只要控制了循环就可以结束任务 2.使用int

5、Java并发编程:Lock

Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock. 也许有朋友会问,既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?这个问题将在下面进行阐述.本文先从synchronized的缺陷讲起,然后再讲述java.util.concurrent.locks包