Java:Swing库和线程安全

我经常听到Swing图书馆缺乏线程安全性的批评。 然而,我不确定我在自己的代码中做什么可能会导致问题:

在什么情况下,Swing不是线程安全的呢?

我应该主动避免做什么?

  1. 不要做长时间运行的任务来响应button,事件等,因为它们在事件线程上。 如果阻塞事件线程,整个GUI将完全没有响应,导致真正生气的用户。 这就是为什么Swing看起来缓慢而有力的原因。

  2. 使用线程,执行程序和SwingWorker来运行不在EDT上的任务(事件调度线程)。

  3. 不要在EDT之外更新或创build小部件。 在美国东部以外地区唯一可以做的就是Component.repaint()。 使用SwingUtilitis.invokeLater确保在EDT上执行某些代码。

  4. 使用EDTdebugging技术和一个聪明的外观和感觉(如物质 ,检查违反EDT)

如果你遵循这些规则,Swing可以制作一些非常有吸引力和响应式的graphics用户界面

一些真正令人敬畏的Swing UI工作的例子: Palantir Technologies 。 注:我不为他们工作,只是一个真棒摇摆的例子。 耻辱没有公开的演示…他们的博客也不错,稀疏,但很好

这是让我很高兴的一个问题,我购买了Robinson&Vorobiev关于Swing的书 。

任何访问java.awt.Component状态的东西都应该在EDT中运行,除了三个例外:特别logging为线程安全的任何东西,比如repaint()revalidate()invalidate() 。 尚未实现的UI中的任何组件; 和Applet的start()之前的Applet中的任何Component。

专门制作线程安全的方法是非常罕见的,通常只需记住那些方法就足够了; 你通常也可以假设没有这样的方法(例如,在一个SwingWorker中包装一个重绘调用是完全安全的)。

实现意味着Component是一个顶级容器(比如JFrame), setVisible(true)show()或者pack()已经被调用,或者已经被添加到已经实现的Component中。 这意味着在main()方法中构buildUI是完全正确的,正如很多教程示例所做的那样,因为在顶级容器中不会调用setVisible(true) ,直到每个Component都添加了它,字体和边框configuration等

出于类似的原因,在它的init()方法中构buildapplet UI是完全安全的,然后在完成所有内容之后调用start()

将Runnables中的后续组件更改包装为发送到invokeLater() ,只需几次即可轻松完成。 我觉得恼人的一件事是从另一个线程读取组件的状态(比如someTextField.getText() )。 从技术上讲,这也必须包装在invokeLater() 。 在实践中,它可以使代码变得很难,而且我经常不打扰,或者在初始事件处理时间(通常是在大多数情况下通常是正确的时间)抓取这些信息。

Swing不是线程安全的(不是很多),但是它是线程敌对的。 如果你开始在一个线程上(而不是EDT)执行Swing,那么当Swing切换到EDT(没有logging)时,可能会出现线程安全问题。 即使Swing文本的目的是线程安全的,也不是线程安全的(例如,追加到文档中,首先需要查找长度,在插入之前可能会改变)。

所以,在EDT上做所有的Swing操作。 请注意,EDT不是主要调用的线程,因此请启动您的(简单)Swing应用程序,如此样板文件:

 class MyApp { public static void main(String[] args) { java.awt.EventQueue.invokeLater(new Runnable() { public void run() { runEDT(); }}); } private static void runEDT() { assert java.awt.EventQueue.isDispatchThread(); ... 

使用像物质这样的智能皮肤的替代方法是创build以下实用方法:

 public final static void checkOnEventDispatchThread() { if (!SwingUtilities.isEventDispatchThread()) { throw new RuntimeException("This method can only be run on the EDT"); } } 

在每一个你需要在事件派发线程上写的方法中调用它。 这样做的好处是可以快速禁用和启用系统范围的检查,例如可能在生产中将其删除。

注意智能皮肤当然可以提供额外的覆盖范围,以及这一点。

除了事件派发线程外,主动避免执行任何Swing工作。 Swing写得很容易扩展,Sun决定使用单线程模型更好。

以上我的build议,我没有问题。 在某些情况下,你可以从其他线程“摆动”,但我从来没有find需要。

如果你使用的是Java 6,那么SwingWorker绝对是处理这个问题的最简单的方法。

基本上你要确保在EventDispatchThread上执行任何改变UI的东西。

这可以通过使用SwingUtilities.isEventDispatchThread()方法来告诉你,如果你在它(通常不是一个好主意 – 你应该知道什么线程是活动的)。

如果您不在EDT上,那么使用SwingUtilities.invokeLater()和SwingUtilities.invokeAndWait()来调用EDT上的Runnable。

如果你更新UI的不在EDT上,你会得到一些令人难以置信的奇怪的行为。 就个人而言,我不认为这是一个Swing的缺陷,你不需要同步所有的线程来提供UI更新,你会得到一些很好的效率 – 你只需要记住这个警告。

“线程不安全”这个短语听起来好像有一些固有的不好(你知道……“安全” – 好,“不安全” – 坏)。 事实上,线程安全是以牺牲代价为代价的 – 线程安全的对象往往是更复杂的实现(而Swing现在已经足够复杂了)。

此外,使用locking(慢速)或比较与交换(复杂)策略可以实现线程安全。 鉴于GUI与人类的交互,往往是不可预测的,难以同步,许多工具包已决定通过一个单一的事件泵渠道的所有事件。 对于Windows,Swing,SWT,GTK以及其他可能的情况都是如此。 其实我不知道一个真正的线程安全的GUI工具包(意味着你可以从任何线程操纵它的对象的内部状态)。

通常做的是,GUI提供了一种处理线程不安全的方法。 正如其他人指出的,Swing总是提供了一些简单的SwingUtilities.invokeLater()。 Java 6包括出色的SwingWorker(可从Swinglabs.org的以前版本中获得)。 还有像Foxtrot这样的第三方库来pipe理Swing上下文中的线程。

Swing的名声是因为devise师采取了轻率的方法,假设开发者会做正确的事情,而不是停止EDT或修改EDT以外的组件。 他们已经明确表示了他们的线程策略,并且由开发者来遵循。

使每个swing API都向EDT发布每个属性集合,invalidate等等,这将使得它是线程安全的,但代价是巨大的减速是微不足道的。 你甚至可以使用AOP自己做。 为了比较,当一个组件被错误的线程访问时,SWT会抛出exception。

这是一个模式,可以用线程来完成。

子类动作(MyAction),并使其doAction线程化。 使构造函数采取一个string名称。

给它一个抽象的actionImpl()方法。

让它看起来像(伪代码警告!)

 doAction(){ new Thread(){ public void run(){ //kick off thread to do actionImpl(). actionImpl(); MyAction.this.interrupt(); }.start(); // use a worker pool if you care about garbage. try { sleep(300); Go to a busy cursor sleep(600); Show a busy dialog(Name) // name comes in handy here } catch( interrupted exception){ show normal cursor } 

您可以logging该任务所花费的时间,下次您的对话框可以显示出合理的估计值。

如果你想要真的很好,也可以在另一个工作线程中睡觉。

请注意,即使模型接口也不是线程安全的。 大小和内容被单独的get方法查询,所以没有办法同步这些方法。

从另一个线程更新模型的状态允许它至less描绘尺寸仍然较大的情况(表格行仍然存在),但内容不再存在。

总是在EDT中更新模型的状态避免这些。

invokeLater()和invokeAndWait()实际上必须在任何不是EDT的线程与GUI组件交互时使用。

它可能在开发过程中工作,但是像大多数并发bug一样,你会看到奇怪的exception出现,看起来完全不相关,并且是非确定性的 – 通常在你真正的用户发货后才会被发现。 不好。

另外,你还没有信心,你的应用程序将会继续在越来越多的核心上工作在未来的CPU上 – 由于它们是真正的并发而不是由操作系统模拟,所以更容易遇到奇怪的线程问题。

是的,它变得丑陋,每一个方法都callback到一个Runnable实例的EDT中,但是这对于你来说是Java。 在我们closures之前,你只能忍受它。

有关线程的更多细节,Allen Holub编写的“驯服Java线程”是一本较老的书,但却是一本很棒的阅读。

Holub真的推动了反应迅速的用户界面和详细的例子,以及如何缓解问题。

http://www.amazon.com/Taming-Java-Threads-Allen-Holub/dp/1893115100 http://www.holub.com/software/taming.java.threads.html

最后在那里爱“如果我是国王”部分。