1 现象及问题
在Swing程序中,经常能看到如下这种代码:
为何用invokeLater,而不直接调用呢?
大多数Swing的API非线程安全,不能在任意地方调用,应该只在EDT中调用。
Swing的线程安全靠事件队列和EDT来保证。
EventQueue的派发机制由单独的一个线程 - 事件派发线程(EDT)管理。
Swing将GUI请求放入一个事件队列中执行。通过EDT,使得非线程安全的Swing函数库避开了并发问题。
3 Swing 中的线程分类
一个Swing程序中一般有下面三种类型的线程:
- 初始化线程(Initial Thread)
- 每个程序必须有一个main方法作为程序的入口。
该方法运行在初始化或启动线程上。初始化线程读取程序参数并初始化一些对象。
在许多Swing程序中,该线程主要目的是启动程序的GUI。创建UI的点,也就是程序开始将控制权转交给UI时的点。
一旦GUI启动后,对大多数事件驱动的桌面程序,初始化线程的工作就结束了。
UI事件调度线程(EDT)
Swing程序只有一个EDT,负责GUI组件的绘制和更新,调用程序的事件处理器来响应用户交互。
所有事件处理都是在EDT执行,程序同UI组件和其基本数据模型的交互只允许在EDT上进行。
所有运行在EDT上的任务应该尽快完成,以便UI能及时响应用户输入。
任务线程(Worker Thread)
4 Swing 编程铁律
4.1 必须通过EDT刷新组件
从其他线程访问UI组件及其事件处理器会导致界面更新和绘制错误
4.2 禁止在EDT执行其他耗时操作
在EDT上执行耗时任务会使程序失去响应,这会使GUI事件阻塞在队列中得不到处理
4.3 耗时操作放在独立的任务线程
通过SwingWorker启动。应使用独立的任务线程来执行耗时计算或输入输出密集型任务。
- 比如同数据库通信
- 访问网站资源、读写大树据量的文件。
任何干扰或延迟UI事件的处理只应出现在独立任务线程中。
- 在初始化线程(即禁止在main方法中直接创建Frame,在初始化线程中应使用invokeLater初始化GUI)
- 任务线程同Swing组件或其缺省数据模型进行的交互
都是非线程安全性操作。
通过SwingWorker类的管理,隔离EDT和任务线程,使它们各负其责
- EDT 绘制和更新界面,并响应用户输入
- 任务线程,执行和界面无直接关系的耗时任务和I/O密集型操作
5 事件队列
在计算机数据结构中,队列是一个特殊的数据结构。
- 它是线性的
- 元素是先进先出的,进入队列的元素必须从末端进入,先入队的元素先得到执行,后入队的元素等待前面的元素执行完毕出队后才能执行,队列的处理方式是执行完一个再执行下一个
队列与线程安全是无关的,不过要想将队列保证线程安全,只需要仿照生产者/消费者模式加上线程的等待/通知即可。