SwingAPI的设计目标是强大灵活和易用非凡地我们希望能让程序员们方便地建立新的Swing组件不论是从头开始还是通过扩展我们所提供的一些组件出于这个目的我们不要求Swing组件支持多线程访问相反我们向组件发送请求并在单一线程中执行请求本文讨论线程和Swing组件目的不仅是为了帮助你以线程安全的方式使用SwingAPI而且解释了我们为什么会选择现在这样的线程方案本文包括以下内容
单线程规则Swing线程在同一时刻仅能被一个线程所访问一般来说这个线程是事件派发线程规则的例外有些操作保证是线程安全的事件分发假如你需要从事件处理或绘制代码以外的地方访问UI那么你可以使用SwingUtilities类的invokeLater要求在事件派发线程中执行某些代码这个方法会立即返回不会等待代码执行完毕invokeAndWait行为与invokeLater类似除了这个方法会等待代码执行完毕一般地你可以用invokeLater来代替这个方法下面是一些使用这几个API的例子请同时参阅《TheJavaTutorial》中的BINGOexample尤其是以下几个类CardWindowControlPanePlayer和OverallStatusPane
使用invokeLater方法你可以从任何线程调用invokeLater方法以请求事件派发线程运行特定代码你必须把要运行的代码放到一个Runnable对象的run方法中并将此Runnable对象设为invokeLater的参数invokeLater方法会立即返回不等待事件派发线程执行指定代码这是一个使用invokeLater方法的例子
RunnabledoWorkRunnable=newRunnable
};
SwingUtilitiesinvokeLater;使用invokeAndWait方法invokeAndWait方法和invokeLater方法很相似除了invokeAndWait方法会等事件派发线程执行了指定代码才返回在可能的情况下你应该尽量用invokeLater来代替invokeAndWait假如你真的要使用invokeAndWait请确保调用invokeAndWait的线程不会在调用期间持有任何其他线程可能需要的锁
这是一个使用invokeAndWait的例子
voidshowHelloThereDialogthrowsException
};
SwingUtilitiesinvokeAndWait;
}
类似地假设一个线程需要对GUI的状态进行存取比如文本域的内容它的代码可能类似这样
voidprintTextField
throwsException
};
SwingUtilitiesinvokeAndWait;
Systemoutprintln;}
假如你能避免使用线程最好这样做线程可能难于使用并使得程序的debug更困难一般来说对于严格意义下的GUI工作线程是不必要的比如对组件属性的更新不管怎么说有时候线程是必要的下列情况是使用线程的一些典型情况执行一项费时的任务而不必将事件派发线程锁定例子包括执行大量计算的情况会导致大量类被装载的情况和为网络或磁盘I/O而阻塞的情况重复地执行一项操作通常在两次操作间间隔一个预定的时间周期要等待来自客户的消息你可以使用两个类来帮助你实现线程SwingWorker创建一个后台线程来执行费时的操作Timer创建一个线程来执行或多次执行某些代码在两次执行间间隔用户定义的延迟使用SwingWorker类SwingWorker类在SwingWorkerjava中实现这个类并不包含在Java的任何发行版中所以你必须单独下载它SwingWorker类做了所有实现一个后台线程所需的骯髒工作虽然许多程序都不需要后台线程后台线程在执行费时的操作时仍然是很有用的它能提高程序的性能观感
SwingWorkersanexampleofusingSwingWorker要使用SwingWorker类你首先要实现它的一个子类在子类中你必须实现construct方法还包含你的长时间操作当你实例化SwingWorker的子类时SwingWorker创建一个线程但并不启动它你要调用你的SwingWorker对象的start方法来启动线程然后start方法会调用你的construct方法当你需要construct方法返回的对象时可以调用SwingWorker类的get方法这是一个使用SwingWorker类的例子
//在main方法中
finalSwingWorkerworker=newSwingWorker
};
workerstart;
//在动作事件处理方法中
JOptionPaneshowMessageDialog)
当程序的main方法调用start方法SwingWorker启动一个新的线程来实例化ExpensiveDialogComponentmain方法还构造了由一个窗口和一个按钮组成的GUI当用户点击按钮程序将阻塞假如必要阻塞到ExpensiveDialogComponent创建完成然后程序显示一个包含ExpensiveDialogComponent的模式对话框你可以在MyApplicationjava找到整个程序使用Timer类Timer类通过一个ActionListener来执行或多次执行一项操作你创建定时器的时候可以指定操作执行的频率并且你可以指定定时器的动作事件的监听者启动定时器后动作监听者的actionPerformed方法会被调用来执行操作定时器动作监听者定义的actionPerformed方法将在事件派发线程中调用这意味着你不必在其中使用invokeLater方法这是一个使用Timer类来实现动画循环的例子
publicclassAnimatorApplicationTimer
extendsJFrameimplementsActionListener
publicvoidstartAnimationelse
}
publicvoidstopAnimation
publicvoidactionPerformed
}
在一个线程中执行所有的用户界面代码有这样一些优点组件开发者不必对线程编程有深入的理解像ViewPoint和Trestle这类工具包中的所有组件都必须完全支持多线程访问使得扩展非常困难尤其对不精通线程编程的开发者来说最近的一些工具包如SubArctic和IFC都采用和Swing类似的设计事件以可预知的次序派发invokeLater排队的runnable对象从鼠标和键盘事件定时器事件绘制请求的同一个队列派发在一些组件完全支持多线程访问的工具包中组件的改变被变化无常的线程调度程序穿插到事件处理过程中这使得全面测试变得困难甚至不可能更低的代价尝试小心锁住临界区的工具包要花费实足的时间和空间在锁的治理上每当工具包中调用某个可能在客户代码中实现的方法时工具包都要保存它的状态并释放所有锁以便客户代码能在必要时获得锁当控制权交回到工具包工具包又必须重新抓住它的锁并恢复状态所有应用程序都不得不负担这一代价即使大多数应用程序并不需要对GUI的并发访问这是的SubArcticJavaToolkit的对在工具包中支持多线程访问的问题的描述我们的基本信条是当设计和建造多线程应用程序尤其是那些包括GUI组件的应用程序时必须保证极端小心线程的使用可能会很有欺骗性在许多情况下它们表现得能够极好的简化编成使得设计专注于单一任务的简单自治实体成为可能在一些情况下它们的确简化了设计和编码然而在几乎所有的情况下它们都使得调试测试和维护的困难大大增加甚至成为不可能无论大多数程序员所受的练习他们的经验和实践还是我们用来帮助自己的工具都不是能够用来对付非决定论的例如全面测试在bug依靠于时间时是几乎不可能的尤其对于Java来说一个程序要运行在许多不同类型的机器的操作系统平台上并且每个程序都必须在抢先和非抢先式调度下都能正常工作由于这些固有的困难我们力劝你三思是否绝对有使用线程的必要尽管如此有些情况下使用线程是必要的所以subArctic提供了一个线程安全的访问机制
本章讨论了这一机制和怎样在一个独立线程中安全地操作交互树他们所说的线程安全机制非常类似于SwingUtilities类提供的invokeLater和invokeAndWait方法