Java:Swing 库和线程安全

Posted

技术标签:

【中文标题】Java:Swing 库和线程安全【英文标题】:Java: Swing Libraries & Thread Safety 【发布时间】:2010-09-15 23:43:39 【问题描述】:

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

Swing 不是线程安全的事实在什么情况下发挥作用?

我应该积极避免做什么?

【问题讨论】:

【参考方案1】:

    切勿响应按钮、事件等执行长时间运行的任务,因为它们位于事件线程上。如果您阻止事件线程,整个 GUI 将完全没有响应,从而导致用户非常生气。这就是为什么 Swing 看起来缓慢而硬朗的原因。

    使用线程、执行程序和 SwingWorker 来运行不在 EDT 上的任务(事件分派线程)。

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

    使用EDT Debug Techniques 和智能外观(如Substance,用于检查EDT 违规)

如果您遵循这些规则,Swing 可以制作一些非常吸引人且响应迅速的 GUI

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

【讨论】:

还有一个 invokeAndWait() 方法,但尽可能使用 invokeLater()。 如果您想要死锁,请使用 invokeAndWait。 ;) 同意。调用和等待是非常可怕的。考虑一下:blog.palantirtech.com/2008/02/21/invokeandnotwaiting 好点。你能告诉我为什么 swing 不像 AWT 那样线程安全吗?【参考方案2】:

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

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

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

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

出于类似的原因,在 init() 方法中构建您的小程序 UI 并在构建完成后调用 start() 是非常安全的。

在 Runnables 中包装后续组件更改以发送到 invokeLater() 只需执行几次即可轻松完成。我觉得烦人的一件事是从另一个线程读取组件的状态(例如,someTextField.getText())。从技术上讲,这也必须包含在 invokeLater() 中;在实践中,它可以使代码快速变得丑陋,我通常不会打扰,或者我很小心地在初始事件处理时获取该信息(无论如何在大多数情况下通常是正确的时间)。

【讨论】:

“UI 中尚未实现的组件”——不再推荐做法。无论是否实现,您都应该始终在 EDT 上使用您的组件。 假设你是对的,这让我想知道他们为什么会推荐这个。考虑到它将如何影响大量教程代码,这是一个重大变化。 ...事实上,我找不到提出此建议的 Java 6 教程;示例代码仍然在主线程中构建 UI。你有这方面的资料吗? @Paul 是真的,请参阅***.com/questions/491323/… 等等。这是回顾性的,而且很痛苦。 哇。很好的发现。这会影响我的很多旧代码。奇怪的是,这五年来我没有写太多的 UI 代码,想想看,当我写的时候,我从来没有遇到过死锁,所以我从来没有注意到。【参考方案3】:

不仅仅是 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();
        ...

【讨论】:

【参考方案4】:

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

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

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

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

【讨论】:

【参考方案5】:

除了在事件调度线程上,积极避免做任何 Swing 工作。 Swing 被编写为易于扩展,Sun 认为单线程模型更适合此。

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

【讨论】:

【参考方案6】:

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

基本上,您要确保更改 UI 的任何操作都在 EventDispatchThread 上执行。

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

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

如果您更新的 UI 不在 EDT 上,您会得到一些非常奇怪的行为。就我个人而言,我不认为这是 Swing 的缺陷,通过不必同步所有线程来提供 UI 更新,您可以获得一些不错的效率 - 您只需要记住这个警告。

【讨论】:

【参考方案7】:

“线程不安全”这句话听起来好像有一些天生不好的东西(你知道……“安全”- 好;“不安全”- 坏)。现实情况是,线程安全是有代价的——线程安全对象的实现通常要复杂得多(尽管 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 会抛出异常。

【讨论】:

【参考方案8】:

这里有一个模式可以让你轻松地挥杆。

Sublass Action (MyAction) 并使其成为 doAction 线程。 使构造函数采用 String NAME。

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

您可以记录该任务所花费的时间,下次您的对话框可以显示一个不错的估计。

如果你想变得非常好,也可以在另一个工作线程中睡觉。

【讨论】:

【参考方案9】:

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

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

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

【讨论】:

【参考方案10】:

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

它可能在开发过程中起作用,但与大多数并发错误一样,您会开始看到奇怪的异常出现,这些异常似乎完全不相关,并且是非确定性的 - 通常在您被真实用户发布后发现。不好。

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

是的,将每个方法调用都包装回 EDT 中的 Runnable 实例会变得很丑陋,但这对您来说是 Java。在我们关闭之前,您只需要忍受它。

【讨论】:

【参考方案11】:

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

Holub,真正促进响应式 UI 和详细示例以及如何缓解问题。

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

喜欢最后的“如果我是国王”部分。

【讨论】:

以上是关于Java:Swing 库和线程安全的主要内容,如果未能解决你的问题,请参考以下文章

Java:测试对“非线程安全”方法的线程访问

Swing:不使用 EDT 创建 GUI 是不是安全? [复制]

Java中 EvenQueue.invokeLater用法

Java线程安全和非线程安全

如何确保Java线程安全?

有效地以线程安全的方式使用BufferedImage?