2008-10-08 221 views
22

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

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

我应该主动避免做什么?

回答

26
  1. 从不做长时间运行的任务来响应按钮,事件等,因为它们在事件线程中。如果阻塞事件线程,则整个GUI将完全无响应,从而导致非常生气的用户。这就是为什么Swing看起来很慢而且很硬。

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

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

  4. 使用EDT Debug Techniques和一个聪明的外观和感觉(如Substance,其检查EDT违规)

如果你遵循这些规则,秋千可以做一些非常有吸引力的,反应的GUI

的一些非常棒的Swing UI工作示例:​​。注意:我不为他们工作,只是一个令人敬畏的挥杆的例子。耻辱没有公开演示...他们的blog也不错,稀疏但很好

+1

还有一个invokeAndWait()方法,但尽可能使用invokeLater()。 – Powerlord 2008-10-08 12:16:55

+1

如果您想要死锁,请使用invokeAndWait。 ;) – 2008-10-08 13:03:18

+0

或按小时支付。 :-) – 2008-10-08 15:14:55

3

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

我没有问题,同时遵循我的建议。在某些情况下,你可以从其他线程“摆动”,但我从来没有找到需要。

8

这不仅仅是Swing不是线程安全的(不是很多),但它是线程敌对的。如果你开始在单线程(EDT之外)上执行Swing东西,那么当Swing切换到EDT(未记录)时,可能会出现线程安全问题。即使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(); 
     ... 
2

如果您使用的是Java 6的SwingWorker那么肯定是要对付这种最简单的方法。

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

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

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

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

11

这是让我很高兴的其中一个问题,我购买了Robinson & Vorobiev's book on Swing

凡是访问java.awt.Component的状态应该在美国东部时间内运行,有三个例外:任何具体的记录为线程安全的,如repaint()revalidate()invalidate(); UI中的任何组件尚未被实现;以及Applet的start()之前的Applet中的任何组件。

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

实现意味着该组件可以是一个顶层容器(像的JFrame),其上的setVisible(true)show(),或任何pack()已被调用,或者它已被添加到一个实现组件。这意味着在main()方法中构建用户界面非常好,正如很多教程示例所做的那样,因为在顶级容器上不会调用setVisible(true),直到每个组件都已添加到它,配置了字体和边框等等

由于类似的原因,在init()方法中构建小应用程序用户界面非常安全,并且在构建完所有方法后再调用start()

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

1

这是一个模式,可以让线程自由摆动。

子类动作(MyAction)并使其成为doAction线程。 使构造函数采用字符串名称。

给它一个抽象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 
} 

可以录制下一次拍摄任务的时间,而且,你的对话框可以显示一个体面的估计。

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

2

短语'线程不安全'听起来像是有些天生不好的东西(你知道......'安全' - 好;'不安全' - 坏)。现实情况是,线程安全的代价是 - 线程安全的对象通常实现起来更加复杂(并且Swing足够复杂,即使是这样)。

此外,线程安全性可以使用锁定(缓慢)或比较和交换(复杂)策略。鉴于GUI与人类交互,这往往是不可预知的并且难以同步,许多工具包已决定通过单个事件泵来引导所有事件。这对于Windows,Swing,SWT,GTK以及其他人来说都是如此。实际上,我不知道一个真正的线程安全的GUI工具包(这意味着你可以从任何线程操纵其对象的内部状态)。

通常做的是,GUI提供了一种处理线程不安全的方法。正如其他人所指出的那样,Swing总是提供了一些简单的SwingUtilities.invokeLater()。 Java 6包含优秀的SwingWorker(可用于Swinglabs.org的以前版本)。还有Foxtrot等第三方库管理Swing上下文中的线程。

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

让每个挥杆API向EDT发布每个属性集合,无效等等,这将使其线程安全,但代价是大幅度减速,这是微不足道的。你甚至可以使用AOP自己做。为了比较,SWT在从一个错误的线程访问一个组件时抛出异常。

1

请注意,即使模型接口都不是线程安全的。大小和内容使用单独的get方法查询,因此无法同步这些方法。

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

始终在EDT中更新模型的状态可避免这些情况。

1

当您与任何非EDT EDT线程的GUI组件进行任何交互时,都必须使用invokeLater()和invokeAndWait()。

它可能在开发过程中工作,但像大多数并发错误一样,您会看到奇怪的异常出现,看起来完全不相关,并且非确定性地发生 - 通常在真实用户发货后发现。不好。另外,你还没有信心,你的应用程序将继续在越来越多的内核的未来CPU上工作 - 由于它们是真正的并发而不是仅仅由操作系统模拟的,所以更容易遇到奇怪的线程问题。

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

4

使用智能皮肤类物质的替代方法是创建下面的实用方法:

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

调用它的每一个方法你写的要求是事件调度线程上。这样做的一个优点是可以快速禁用和启用全系统检查,例如可能会在生产中将其删除。

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