Java:Swing Libraries&线程安全

时间:2008-10-08 11:47:27

标签: java multithreading swing

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

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

我应该主动避免做什么?

11 个答案:

答案 0 :(得分:26)

  1. 永远不要执行长时间运行的任务以响应事件线程上的按钮,事件等。如果您阻止事件线程,整个GUI将完全没有响应,导致真正生气的用户。这就是为什么Swing看起来很慢而且很脆弱的原因。

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

  3. 不要在EDT之外更新或创建小部件。几乎可以在EDT之外进行的唯一调用就是Component.repaint()。使用SwingUtilitis.invokeLater确保在EDT上执行某些代码。

  4. 使用EDT Debug Techniques和智能外观(如Substance,检查是否违反EDT)

  5. 如果您遵循这些规则,Swing可以制作一些非常有吸引力和响应的GUI

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

答案 1 :(得分:11)

这是让我很高兴购买Robinson & Vorobiev's book on Swing的问题之一。

任何访问java.awt.Component状态的内容都应该在EDT中运行,但有三个例外:任何具体记录为线程安全的内容,例如repaint(),{ {1}}和revalidate(); UI中尚未实现的任何组件;和该Applet的invalidate()之前的Applet中的任何组件都已被调用。

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

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

出于类似的原因,在setVisible(true)方法中构建applet UI非常安全,然后在构建完所有内容后调用init()

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

答案 2 :(得分:8)

不仅仅是Swing不是线程安全的(不是很多),但它的线程是敌对的。如果你开始在一个线程(除了EDT)上做Swing的东西,那么当Swing切换到EDT(没有记录)的情况下,可能存在线程安全问题。即使是针对线程安全的Swing文本也没有用于线程安全(例如,要附加到文档,首先需要找到长度,这可能会在插入之前发生变化)。

所以,在EDT上做所有Swing操作。注意EDT不是调用main的线程,所以启动你的(简单)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();
        ...

答案 3 :(得分:4)

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

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

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

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

答案 4 :(得分:3)

除了事件调度线程外,主动完全避免执行任何Swing工作。 Swing编写得很容易扩展,Sun认为单线程模型对此更好。

在遵循上述建议的同时,我没有遇到任何问题。在某些情况下,你可以从其他线程“摆动”,但我从未发现需要。

答案 5 :(得分:2)

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

基本上,您希望确保在EventDispatchThread上执行更改UI的任何内容。

这可以通过使用SwingUtilities.isEventDispatchThread()方法告诉您是否在其中(通常不是一个好主意 - 您应该知道哪个线程处于活动状态)。

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

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

答案 6 :(得分:2)

“线程不安全”这句话听起来有些内在不好(你知道......'安全' - 好;'不安全' - 坏)。实际情况是线程安全需要付出代价 - 线程安全对象实现起来往往更复杂(而Swing就足够复杂了。)

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

通常做的是GUI提供了一种处理线程不安全的方法。正如其他人所说,Swing总是提供一些简单的SwingUtilities.invokeLater()。 Java 6包括优秀的SwingWorker(可从Swinglabs.org的早期版本获得)。还有像Foxtrot这样的第三方库用于在Swing上下文中管理线程。

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

让每个swing API为每个属性集,无效等向EDT发布作业都是微不足道的,这将使其成为线程安全的,但代价是大量减速。您甚至可以使用AOP自己完成。为了进行比较,当从错误的线程访问组件时,SWT会抛出异常。

答案 7 :(得分:1)

这是makng swing thread-freindly的模式。

Sublass Action(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
}

您可以记录任务所需的时间,下次您的对话框可以显示合理的估算值。

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

答案 8 :(得分:1)

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

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

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

答案 9 :(得分:1)

当您从任何非EDT的线程进行与GUI组件的任何交互时,必须使用

invokeLater()和invokeAndWait()。

它可能在开发过程中起作用,但是像大多数并发错误一样,你会开始看到奇怪的异常出现,看起来完全不相关,并且非确定性地发生 - 通常在真实用户发货后发现。不好。

此外,您已经不相信您的应用程序将继续在具有越来越多内核的未来CPU上运行 - 由于它们真正并发而不仅仅是由操作系统模拟,因此更容易遇到奇怪的线程问题。

是的,在Runnable实例中将每个方法调用包装回EDT都很难看,但这对你来说就是Java。在我们关闭之前,你必须忍受它。

答案 10 :(得分:1)

有关线程的更多详细信息,Allen Holub的Taming Java Threads是一本较旧的书,但读起来很棒。

Holub,真正推广响应式用户界面和详细示例以及如何缓解问题。

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

最后爱上“如果我是国王”部分。