【问题标题】:If EDT is a separate thread, why does invokeLater wait for the main thread to finish in this example?如果 EDT 是一个单独的线程,为什么在这个例子中 invokeLater 等待主线程完成?
【发布时间】:2021-06-15 16:10:55
【问题描述】:

所以如果 Event Dispatch Thread 是一个独立于主线程的线程,那让我认为下一个代码会输出

Before
Runnable
true
After

但是当我运行它时,就好像 EDT 在运行 invokeLater(..) 方法中的代码块之前等待主线程完成。输出是:

Before
After
Runnable
true

代码:

public class Main {
    public static void main(String[] args) {
        System.out.println("Before");
        SwingUtilities.invokeLater(() -> {
            System.out.println("Runnable");
            System.out.println(SwingUtilities.isEventDispatchThread());
        });

        System.out.println("After");
    }
}

但是,如果我将 invokeLater(..) 替换为 invokeAndWait(..),那么我会得到

Before
Runnable
true
After

这让我觉得 EDT 并不是一个真正的单独线程,或者至少在这个例子中它的行为或者只是在我看来不是。你怎么解释这个?

【问题讨论】:

  • EDT is temporary queue 在 flush() 之后为空时消失。您可以通过调用 SwingAction 反复“激活”此队列。 InvokeLater and invokeAndWait 在 main() 中实现了不同的逻辑,那么你的输出是正确的。注意在 isEventDispatchThread() 返回 TRUE 的情况下不能调用 invokeAndWait

标签: java multithreading swing event-dispatch-thread invokelater


【解决方案1】:

你怎么解释这个?

当您尝试解释观察到的涉及线程的行为时,几乎适用于每个问题的相同解释:

线程是复杂的野兽,通常添加代码来观察正在发生的事情会影响线程,线程作为一般概念是不可重复的(事情取决于您的音乐播放器中播放的歌曲到月相),以及它不像你想象的那样工作,并且提供的解释和心理模型过于简单化,不成立。

这里不难解释,幸好:

  1. 是的,它确实是一个单独的线程。您可以随时运行Thread.currentThread() 来查看。

  2. 线程不是什么巫术魔法瞬时的东西,你的 CPU 也不是秘密的一批完全独立的计算机。

invokeLater 调用获取您的代码,将其添加到队列中,然后 ping EDT 以唤醒并处理该队列,该队列现在有 1 个项目。这个队列有很多流量;当您将另一个窗口移到您的窗口上时,EDT 会收到重新绘制的 ping。当您单击一个按钮时,它也会被添加到队列中,等等。

不管该队列上的流量如何,唤醒线程的行为都不是即时的。你的主线程开始写After much 比你的计算机系统更快(不是人类可以观察到它,计算机的运行速度比我们用眼球观察到的快几百万倍)真正唤醒那个线程。

在那里投一颗炸弹看看效果。例如,在sysout("After"); 之前添加Thread.sleep(1000); 并观察会发生什么。

当然你会观察你在使用invokeAndWait 时所做的事情——这将做完全相同的事情(将你的代码添加到队列中,并向线程发送 ping 以唤醒),但它会冻结你的主线。发送到 EDT 的代码将更新一些内部布尔值(“是的,我已经运行到最后”,并向主线程发送 ping 以唤醒,检查该布尔值,然后继续)。因此,它无法继续运行,直到您的代码(打印 Runnable 和 true)运行为止。

换句话说:

你的第二个 sn-p 总是输出你写的东西。

你的第一个 sn-p 会输出,好吧,谁知道呢。该系统可以免费打印B/R/t/A,或B/A/R/t,在极少数情况下甚至可以打印B/R/A/t。另外一个问题是,System.out 很重并会导致同步,因此如果您在分析线程的同时进行打印,那么您观察到的内容通常完全无关紧要。

【讨论】:

  • 还有一件事:在 OP 的示例中,...invokeLater(...) 调用是整个程序中的第一个(也是唯一一个)Swing 调用。这意味着,在 invokeLater 创建之前,EDT 甚至都不存在。我还没有看到源代码,所以我不知道 EDT 在启动时做了多少初始化,甚至在它查看任务队列之前。可能很重要。
【解决方案2】:

它是一个单独的线程,您只是要求当前线程调用 EDT 上的代码并等待它执行。

这就像显式启动一个线程:

Thread t = new Thread(() -> { ... });
t.start();

然后等待它完成:

t.join();

【讨论】:

    【解决方案3】:

    事实上,您最初的假设是不正确的。我们可以从您的代码中唯一假设的是“Before”将在其他任何内容之前输出,并且“Runnable”将在“true”之前输出。

    EDT 代码的执行将在它发生时发生,并且无法保证何时会与您的其他代码相关。

    实际上,EDT 代码很少会在“After”代码执行之前实际执行。向 EDT 提交作业并在主线程中的单行代码可以运行之前将其拾取并运行的开销太大。所以我会期待你实际看到的输出。

    这里真正的重要要点是,除非您专门设计它,否则您永远不应该假设线程之间的任何操作顺序,并且您永远不应该对 EDT 这样做。

    【讨论】:

      猜你喜欢
      • 2017-07-19
      • 2022-01-02
      • 2012-07-22
      • 1970-01-01
      • 1970-01-01
      • 2021-06-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多