Swing API
设计目标是强大、灵活和易用
特别地
我们希望能让
员们方便地建立新
Swing组件
不论是从头开始还是通过扩展我们所提供
些组件
出于这个目
我们不要求Swing组件支持多线程访问
相反
我们向组件发送请求并在单
线程中执行请求
本文讨论线程和Swing组件
目
不仅是为了帮助你以线程安全
方式使用Swing API
而且解释了我们为什么会选择现在这样
线程方案
本文包括以下内容:
单线程规则:Swing线程在同
时刻仅能被
个线程所访问
般来说
这个线程是事件派发线程(event-dispatching thread)
规则
例外:有些操作保证是线程安全
事件分发:如果你需要从事件处理(event-handling)或绘制代码以外
地方访问UI
那么你可以使用SwingUtilities类
invokeLater
或invokeAndWait
思路方法
创建线程:如果你需要创建
个线程——比如用来处理
些耗费大量计算能力或受I/O能力限制
工作——你可以使用
个线程工具类如SwingWorker或Timer
为什么我们这样实现Swing:我们将用
些有关Swing
线程安全
背景资料来结束这篇文章
Swing
规则是:
旦Swing组件被具现化(realized)
所有可能影响或依赖于组件状态
代码都应该在事件派发线程中执行
这个规则可能听起来有点吓人
但对许多简单
来说
你用不着为线程问题操心
在我们深入如何撰写Swing代码的前
让我们先来定义两个术语:具现化(realized)和事件派发线程(event-dispatching thread)
具现化
意思是组建
pa
思路方法已经或可能会被
个作为顶级窗口
Swing组件当
以下思路方法时将被具现化:
Visible(true)、show
或(可能令你惊奇)pack
当
个窗口被具现化
它包含
所有组件都被具现化
另
个具现化
个组件
思路方法是将它放入到
个已经具现化
容器中
稍后你会看到
些对组件具现化
例子
事件派发线程是执行绘制和事件处理
线程
例如
pa
和actionPerformed
思路方法会自动在事件派发线程中执行
另
个将代码放到事件派发线程中执行
思路方法是使用SwingUtilities类
invokeLater
思路方法
所有可能影响
个已具现化
Swing组件
代码都必须在事件派发线程中执行
但这个规则有
些例外:
有些思路方法是线程安全
:在Swing API
文档中
线程安全
思路方法用以下文字标记:
This method is thread safe, although most Swing methods are not.
(这个思路方法是线程安全
尽管大多数Swing思路方法都不是
)
个应用
GUI常常可以在主线程中构建和显示:下面
典型代码是安全
只要没有(Swing或其他)组件被具现化:
public
MyApplication
{
public
void
(String
args)
{
JFrame f =
JFrame("Labels"); // 在这里将各组件
// 加入到主框架……
f.pack
;
f.show
;
// 不要再做任何GUI工作……
}
}
上面所示
代码全部在“
”线程中运行
对f.pack
使得JFrame以下
组件都被具现化
这意味着
f.show
是不安全
且应该在事件派发线程中执行
尽管如此
只要
还没有
个看得到
GUI
JFrame或它
里面
组件就几乎不可能在f.show
返回前收到
个pa
在f.show
的后不再有任何GUI代码
于是所有GUI工作都从主线程转到了事件派发线程
因此前面所讨论
代码实际上是线程安全
个applet
GUI可以在init
思路方法中构造和显示:现有
浏览器都不会在
个applet
init
和start
思路方法被
前绘制它
因而
在
个applet
init
思路方法中构造GUI是安全
只要你不对applet中
对象
show
或
Visible(true)思路方法
要顺便
提
是
如果applet中使用了Swing组件
就必须实现为JApplet
子类
并且
组件应该添加到
JApplet内容窗格(content pane)中
而不要直接添加到JApplet
对任何applet
你都不应该在init
或start
思路方法中执行费时
化操作;而应该启动
个线程来执行费时
任务
下述JComponent思路方法是安全
可以从任何线程
:repa
、revalidate
、和invalidate
repa
和revalidate
思路方法为事件派发线程对请求排队
并分别
pa
和validate
思路方法
invalidate
思路方法只在需要确认时标记
个组件和它
所有直接祖先
监听者列表可以由任何线程修改:
addListenerTypeListener
和removeListenerTypeListener
思路方法总是安全
对监听者列表
添加/删除操作不会对进行中
事件派发有任何影响
注意:revalidate
和旧
validate
思路方法的间
重要区别是
revalidate
会缓存Cache请求并组合成
次validate
这和repa
缓存Cache并组合绘制请求类似
大多数
化后
GUI工作自然地发生在事件派发线程
旦GUI成为可见
大多数
都是由事件驱动
如按钮动作或鼠标点击
这些总是在事件派发线程中处理
不过
总有些
需要在GUI成为可见后执行
些非事件驱动
GUI工作
比如:
在成为可用前需要进行长时间
化操作
:这类
通常应该在
化期间就显示出GUI
然后更新或改变GUI
化过程不应该在事件派发线程中进行;否则
重绘组件和事件派发会停止
尽管如此
在
化的后
GUI
更新/改变还是应该在事件派发线程中进行
理由是线程安全
必须响应非AWT事件来更新GUI
:例如
想象
个服务器
从可能运行在其他机器上
得到请求
这些请求可能在任何时刻到达
并且会引起在
些可能未知
线程中对服务器
思路方法
这个思路方法
怎样更新GUI呢?在事件派发线程中执行GUI更新代码
SwingUtilities类提供了两个思路方法来帮助你在事件派发线程中执行代码:
invokeLater
:要求在事件派发线程中执行某些代码
这个思路方法会立即返回
不会等待代码执行完毕
invokeAndWait
:行为和invokeLater
类似
除了这个思路方法会等待代码执行完毕
般地
你可以用invokeLater
来代替这个思路方法
下面是
些使用这几个API
例子
请同时参阅
The Java Tutorial
中
“BINGO example”
尤其是以下几个类:CardWindow、ControlPane、Player和OverallStatusPane
使用invokeLater
思路方法
你可以从任何线程
invokeLater
思路方法以请求事件派发线程运行特定代码
你必须把要运行
代码放到
个Runnable对象
run
思路方法中
并将此Runnable对象设为invokeLater
参数
invokeLater
思路方法会立即返回
不等待事件派发线程执行指定代码
这是
个使用invokeLater
思路方法
例子:
Runnable doWorkRunnable =
Runnable
{
public void run
{
doWork
;
}
};
SwingUtilities.invokeLater(doWorkRunnable);
使用invokeAndWait
思路方法
invokeAndWait
思路方法和invokeLater
思路方法很相似
除了invokeAndWait
思路方法会等事件派发线程执行了指定代码才返回
在可能
情况下
你应该尽量用invokeLater
来代替invokeAndWait
如果你真
要使用invokeAndWait
请确保
invokeAndWait
线程不会在
期间持有任何其他线程可能需要
锁
这是
个使用invokeAndWait
例子:
void showHelloThereDialog
throws Exception
{
Runnable showModalDialog =
Runnable
{
public void run
{
JOptionPane.showMessageDialog( myMainFrame, "Hello There");
}
};
SwingUtilities.invokeAndWait (showModalDialog);
}
类似地
假设
个线程需要对GUI
状态进行存取
比如文本域
内容
它
代码可能类似这样:
void pr
TextField
throws Exception {
final String
myStrings =
String[2];
Runnable getTextFieldText =
Runnable
{
public void run
{
myStrings[0] = textField0.getText
;
myStrings[1] = textField1.getText
;
}
};
SwingUtilities.invokeAndWait (getTextFieldText);
.out.pr
ln(myStrings[0] + " " + myStrings[1]);}
如果你能避免使用线程
最好这样做
线程可能难于使用
并使得
debug更困难
般来说
对于严格意义下
GUI工作
线程是不必要
比如对组件属性
更新
不管如何说
有时候线程是必要
下列情况是使用线程
些典型情况:
执行
项费时
任务而不必将事件派发线程锁定
例子包括执行大量计算
情况
会导致大量类被装载
情况(如
化)
和为网络或磁盘I/O而阻塞
情况
重复地执行
项操作
通常在两次操作间间隔
个预定
时间周期
要等待来自客户
消息
你可以使用两个类来帮助你实现线程:
SwingWorker:创建
个后台线程来执行费时
操作
Timer:创建
个线程来执行或多次执行某些代码
在两次执行间间隔用户定义
延迟
使用SwingWorker类
SwingWorker类在SwingWorker.java中实现
这个类并不包含在Java
任何发行版中
所以你必须单独下载它
SwingWorker类做了所有实现
个后台线程所需
肮脏工作
虽然许多
都不需要后台线程
后台线程在执行费时
操作时仍然是很有用
它能提高
性能观感
SwingWorker's get
method. Here's an example of using SwingWorker:
要使用SwingWorker类
你首先要实现它
个子类
在子类中
你必须实现construct
思路方法还包含你
长时间操作
当你例子化SwingWorker
子类时
SwingWorker创建
个线程但并不启动它
你要
你
SwingWorker对象
start
思路方法来启动线程
然后start
思路方法会
你
construct
思路方法
当你需要construct
思路方法返回
对象时
可以
SwingWorker类
get
思路方法
这是
个使用SwingWorker类
例子:
...// 在
思路方法中:
final SwingWorker worker =
SwingWorker
{
public Object construct
{
expensiveDialogComponent
;
}
};
worker.start
;
...
// 在动作事件处理思路方法中:
JOptionPane.showMessageDialog (f, worker.get
);
当
思路方法
start
思路方法
SwingWorker启动
个新
线程来例子化ExpensiveDialogComponent
思路方法还构造了由
个窗口和
个按钮组成
GUI
当用户点击按钮
将阻塞
如果必要
阻塞到ExpensiveDialogComponent创建完成
然后
显示
个包含ExpensiveDialogComponent
模式对话框
你可以在MyApplication.java找到整个
使用Timer类
Timer类通过
个ActionListener来执行或多次执行
项操作
你创建定时器
时候可以指定操作执行
频率
并且你可以指定定时器
动作事件
监听者(action listener)
启动定时器后
动作监听者
actionPerformed
思路方法会被(多次)
来执行操作
定时器动作监听者(action listener)定义
actionPerformed
思路方法将在事件派发线程中
这意味着你不必在其中使用invokeLater
思路方法
这是
个使用Timer类来实现动画循环
例子:
public
AnimatorApplicationTimer
extends JFrame implements ActionListener {
...//在这里定义例子变量
Timer timer;
public AnimatorApplicationTimer(...) {
... // 创建
个定时器来
// 来
此对象action handler
timer =
Timer(delay, this);
timer.
InitialDelay(0);
timer.
Coalesce(true);
...
}
public void startAnimation
{
(frozen) {
// 什么都不做
应用户要求
// 停止变换图像
}
{
// 启动(或重启动)动画!
timer.start
;
}
}
public void stopAnimation
{
// 停止动画线程
timer.stop
;
}
public void actionPerformed (ActionEvent e)
{
// 进到下
帧动画
frameNumber
;
// 显示
repa
;
}
...
}
在
个线程中执行所有
用户界面代码有这样
些优点:
组件开发者不必对线程编程有深入
理解:像ViewPo
和Trestle这类工具包中
所有组件都必须完全支持多线程访问
使得扩展非常困难
尤其对不精通线程编程
开发者来说
最近
些工具包如SubArctic和IFC
都采用和Swing类似
设计
事件以可预知
次序派发:invokeLater
排队
runnable对象从鼠标和键盘事件、定时器事件、绘制请求
同
个队列派发
在
些组件完全支持多线程访问
工具包中
组件
改变被变化无常
线程调度
穿插到事件处理过程中
这使得全面测试变得困难甚至不可能
更低
代价:尝试小心锁住临界区
工具包要花费实足
时间和空间在锁
管理上
每当工具包中
某个可能在客户代码中实现
思路方法时(如public类中
任何public和protected思路方法)
工具包都要保存它
状态并释放所有锁
以便客户代码能在必要时获得锁
当控制权交回到工具包
工具包又必须重新抓住它
锁并恢复状态
所有应用
都不得不负担这
代价
即使大多数应用
并不需要对GUI
并发访问
这是
SubArctic Java Toolkit
作者对在工具包中支持多线程访问
问题
描述:
我们
基本信条是
当设计和建造多线程应用
尤其是那些包括GUI组件
应用
时
必须保证极端小心
线程
使用可能会很有欺骗性
在许多情况下
它们表现得能够极好
简化编成
使得设计“专注于单
任务
简单自治实体”成为可能
在
些情况下它们
确简化了设计和编码
然而
在几乎所有
情况下
它们都使得调试、测试和维护
困难大大增加甚至成为不可能
无论大多数
员所受
训练、他们
经验和实战
还是我们用来帮助自己
工具
都不是能够用来对付非决定论
例如
全面测试(这总是困难
)在bug依赖于时间时是几乎不可能
尤其对于Java来说
个
要运行在许多区别类型
机器
操作系统平台上
并且每个
都必须在抢先和非抢先式调度下都能正常工作
由于这些固有
困难
我们力劝你 3思是否绝对有使用线程
必要
尽管如此
有些情况下使用线程是必要
(或者是被其他软件Software包强加
)
所以subArctic提供了
个线程安全
访问机制
本章讨论了这
机制和怎样在
个独立线程中安全地操作交互树
他们所说
线程安全机制非常类似于SwingUtilities类提供
invokeLater
和invokeAndWait
思路方法
延伸阅读
最新评论