解释以下代码是做什么的?

15
java.awt.EventQueue.invokeLater(new Runnable() {
    public void run() {
        new NewJFrame().setVisible(true);
    }
});
请告诉我这段代码实际上做了什么。我需要逐行解释,特别是第一行,并告诉我为什么我们要使用它,在什么情况下我们必须使用它。
5个回答

28
在这个示例中,您可以看到一个匿名类,它派生自Runnable接口。该匿名类重写了接口Runnable的run方法。然后,实例化该匿名类并传递给EventQueue.invokeLater方法,该方法是一个静态方法。这个方法会将对象附加到事件队列中。在事件队列中有许多事件,如键盘事件或鼠标事件等等。有一个线程不断地从此队列中轮询数据。一旦该线程到达在此处实例化的匿名类,它将执行run()方法,该方法将实例化NewJFrame类的对象并将其设置为可见。
这样做的整个目的是new JFrame().setVisible(true)部分不在主线程中执行,而是在事件分派线程中执行。在Swing中,必须在事件分派线程中执行所有修改用户界面的代码。

如果您只是将此代码放入主方法中而没有任何包装,会发生什么情况呢? public static void main(String args[]) {New JFrame().setVisible(true);} - Martin
1
@Martin:那么你就违反了影响UI的代码必须在事件分派线程中执行的规则。这可能会起作用(大多数情况下),但也可能导致许多与竞态条件等相关的错误。很难预测会发生什么。也许你会遇到绘图错误,也许不会。 - yankee
如果我理解正确的话,这是一种编码指南,最佳实践,但不是严格的规则?无论如何,感谢您的回答! - Martin
1
@Martin:不行,这是一条规则。如果你违反它,你会冒着随机的难以调试的问题的风险。这不像“不要把你所有的代码都写在一行中”这样的最佳实践。那会创建难以阅读的代码,但从理论上讲可以工作。不遵守所提到的规则可能会破坏你的应用程序。 - yankee

4

Single-Thread-Model 和 EDT

大多数现代 UI 库采用了 single-thread-model,这意味着所有对 UI 组件的操作必须在同一单线程上完成。为什么?因为允许从多个线程更新 UI 组件会导致混乱,因为大多数 Swing 对象方法不是“线程安全的”。为了简化、高效和强健,采用了 single-thread-model。

在 Swing 中,服务于 single-thread-model 的线程被称为Event Dispatching Thread,即 EDT。它不是由 Swing 提供的,而是由 Abstract Window Toolkit 提供的。

工作线程 vs UI 线程

一个非平凡的 GUI 应用程序通常有许多线程。在现代 GUI 应用程序中,可以有许多工作线程来执行一些不好的工作,但只有一个 UI 线程(Swing 称之为 EDT)来更新 GUI。工作线程通常需要在 GUI 中反映它们的工作进度,因此它们需要与 UI 线程进行通信。那么这种通信是如何发生的呢?

java.awt.EventQueue

通信通过一个消息队列模型发生,java.awt.EventQueue 是提供全局事件队列的类。这个全局事件队列作为与 EDT 通信的通道。EDT 从此 EventQueue 中获取消息并相应地更新 UI 组件。如果程序的其他部分要操纵 UI,则该代码部分应调用 EventQueue.invokeLater()EventQueue.invokeAndWait() 将消息排入 EventQueue。EDT将处理在 EventQueue 中挂起的所有消息,并最终到达消息。

主线程

您的代码段通常驻留在 main() 线程中,main 线程可以视为一种 worker thread。只有不是通过将消息发布到 EventQueue 来更新 GUI,而是初始化 GUI。无论如何,启动也可以被视为一种工作。

初始化 GUI 后,主线程将退出,EDT 将防止进程退出。

另一个好的解释:

Java Event-Dispatching Thread explanation

一个有趣的文章:

Multi-threaded toolkit, a failed dream?


看起来你最后那个链接失效了或者被放错了位置?你有那篇文章的链接吗?听起来很有趣。 - code11

2

这是一段代码块,它被指示在稍后执行(有时被称为延迟)。内部类(new Runnable() {...})实际上允许您传递将要运行的代码块。invokeLater方法保证代码块将被运行,但不保证何时运行。有时立即运行某些代码是不安全的,而且手动进行多线程编程过于冗长。因此Java提供了这个实用方法来安全地运行代码。代码将很快运行,但直到可以安全地运行为止。


1

invokeLater调用将指定的可运行对象放入队列中以便稍后处理。也就是说,在invokeLater方法调用返回时,run()方法内部的代码尚未运行。

这种类型的代码有两个典型用例。

  1. 当前正在执行的代码在后台线程中运行。后台线程无法访问大多数Swing API。在此处阅读更多关于此原因的信息。如果当前线程已经是UI线程,则没有理由,可以安全地删除该调用。
  2. 当前块必须退出,即代码到达最后一个括号。这可能会导致资源被释放等。这不太常见。

匿名类作为参数传递给invokeLater调用。它与此代码相同。

private void foo()
{
  java.awt.EventQueue.invokeLater(new JFrameCreator());
}
private class JFrameCreator implements Runnable
{
  public void run() {
    new NewJFrame().setVisible(true);
  }
}

这是一个很好的解释..只是告诉我为什么我们在这里使用runnable()? - Deepak
我猜 (new JFrameCreator()) 会创建一个匿名类。这是否意味着当此线程被处理时,run() 方法会自动执行?如果我问了完全错误的问题,请原谅。我只是想了解 run() 和 (new JFrameCreator())。 - Deepak
不好意思,这样行不通。类JFrameCreator必须实现Runnable接口(只需编写class JFrameCreator implements Runnable)。而且@Deepak:在这个示例中没有涉及匿名类。 - yankee
@yankee:只有当JFrameCreator类被实现为可运行时,run()方法才会被调用,对吗?或者如果我们为JFrameCreator提供一个构造函数,并在构造函数内调用setVisible(true)呢?那样可以吗? - Deepak
@yankee:好的,那非常完美。我已经很清楚 runnable() 和 invokeLater() 的作用了。谢谢。 - Deepak
显示剩余2条评论

1

源代码

invokeLater() 方法以 Runnable 对象作为其参数。它将该对象发送到事件分派线程,该线程执行 run() 方法。这就是为什么始终可以安全地让 run() 方法执行 Swing 代码的原因。

-IvarD


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接