java 事件调度线程是如何工作的?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2484425/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 21:24:57  来源:igfitidea点击:

How does the event dispatch thread work?

javauser-interfaceeventsmultithreadinginvoke

提问by Roman

With the help of people on stackoverflowI was able to get the following working code of a simple GUI countdown (which just displays a window counting down seconds). My main problem with this code is the invokeLaterstuff.

stackoverflow上的人的帮助下,我能够获得以下简单 GUI 倒计时的工作代码(它只显示一个倒计时的窗口)。我对这段代码的主要问题是这些invokeLater东西。

As far as I understand invokeLater, it sends a task to the event dispatching thread (EDT) and then the EDT executes this task whenever it "can" (whatever that means). Is that right?

据我了解invokeLater,它向事件调度线程 (EDT) 发送一个任务,然后 EDT 在“可以”时(无论这意味着什么)执行此任务。是对的吗?

To my understanding, the code works like this:

据我了解,代码是这样工作的:

  1. In the mainmethod we use invokeLaterto show the window (showGUImethod). In other words, the code displaying the window will be executed in the EDT.

  2. In the mainmethod we also start the counterand the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right?

  3. The counteris executed in a separate thread and periodically it calls updateGUI. updateGUIis supposed to update the GUI. And the GUI is working in the EDT. So, updateGUIshould also be executed in the EDT. It is the reason why the code for the updateGUIis enclosed in invokeLater. Is that right?

  1. main我们invokeLater用来显示窗口的showGUI方法(方法)中。换句话说,显示窗口的代码将在 EDT 中执行。

  2. 在该main方法中,我们还启动了counter计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。对?

  3. counter是在一个单独的线程中执行,并定期调用updateGUIupdateGUI应该更新GUI。GUI 正在 EDT 中工作。所以,updateGUI也应该在EDT中执行。这就是为什么 的代码updateGUI包含在invokeLater. 是对的吗?

What is not clear to me is why we call the counterfrom the EDT. Anyway, it is not executed in the EDT. It starts immediately, a new thread and the counteris executed there. So, why can we not call the counterin the main method after the invokeLaterblock?

我不清楚的是为什么我们counter从 EDT调用。无论如何,它不在 EDT 中执行。它立即启动,一个新线程并在counter那里执行。那么,为什么我们不能counterinvokeLater块之后调用main 方法呢?

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

public class CountdownNew {

    static JLabel label;

    // Method which defines the appearance of the window.   
    public static void showGUI() {
        JFrame frame = new JFrame("Simple Countdown");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        label = new JLabel("Some Text");
        frame.add(label);
        frame.pack();
        frame.setVisible(true);
    }

    // Define a new thread in which the countdown is counting down.
    public static Thread counter = new Thread() {
        public void run() {
            for (int i=10; i>0; i=i-1) {
                updateGUI(i,label);
                try {Thread.sleep(1000);} catch(InterruptedException e) {};
            }
        }
    };

    // A method which updates GUI (sets a new value of JLabel).
    private static void updateGUI(final int i, final JLabel label) {
        SwingUtilities.invokeLater( 
            new Runnable() {
                public void run() {
                    label.setText("You have " + i + " seconds.");
                }
            }
        );
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                showGUI();
                counter.start();
            }
        });
    }

}

回答by Kiril

If I understand your question correctly you're wonder why you can't do this:

如果我正确理解您的问题,您会想知道为什么不能这样做:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
        }
    });
    counter.start();
}

The reason why you can't do it is because the scheduler makes no guarantees... just because you invoked showGUI()and then you invoked counter.start()doesn't mean that the code in showGUI()will be executed before the code in the run method of the counter.

你不能这样做的原因是因为调度程序没有保证......仅仅因为你调用showGUI()然后你调用counter.start()并不意味着中的代码showGUI()将在counter.

Think of it this way:

可以这样想:

  • invokeLater starts a thread and that thread isschedules an asynchronous event on the EDT which is tasked with creatingthe JLabel.
  • the counter is a separate thread that depends on the JLabelto exists so it can call label.setText("You have " + i + " seconds.");
  • invokeLater的启动一个线程和线程的调度,其任务是在美国东部时间异步事件产生JLabel
  • 计数器是一个独立的线程,它依赖于JLabel存在所以它可以调用label.setText("You have " + i + " seconds.");

Now you have a race condition:JLabelmust be created BEFORE the counterthread starts, if it's not created before the counter thread starts, then your counter thread will be calling setTexton an uninitialized object.

现在你有一个竞争条件:JLabel必须在counter线程启动之前创建,如果它没有在计数器线程启动之前创建,那么你的计数器线程将调用setText一个未初始化的对象。

In order to ensure that the race condition is eliminated we must guarantee the order of execution and one wayto guarantee it is to execute showGUI()and counter.start()sequentially on the same thread:

为了保证比赛的条件被消除,我们必须保证执行和顺序的一种方式,以保证它是执行showGUI()counter.start()在同一线程上按顺序:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

Now showGUI();and counter.start();are executed from the same thread, thus the JLabelwill be created before the counteris started.

NowshowGUI();counter.start();是从同一个线程执行的,因此JLabel将在counter启动之前创建。

Update:

更新:

Q:And I do not understand what is special about this thread.
A:Swing event handling code runs on a special thread known as the event dispatch thread. Most code that invokes Swing methods also runs on this thread. This is necessary because most Swing object methods are not "thread safe": invoking them from multiple threads risks thread interference or memory consistency errors. 1

Q:So, if we have a GUI why should we start it in a separate thread?
A:There is probably a better answer than mine, but if you want to update the GUI from the EDT (which you do), then you have to start it from the EDT.

Q:And why we cannot just start the thread like any other other thread?
A:See previous answer.

Q:Why we use some invokeLater and why this thread (EDT) start to execute request when it's ready. Why it is not always ready?
A:The EDT might have some other AWT events it has to process. invokeLaterCauses doRun.run() to be executed asynchronously on the AWT event dispatching thread. This will happen after all pending AWT events have been processed. This method should be used when an application thread needs to update the GUI. 2

问:我不明白这个线程有什么特别之处。
答:Swing 事件处理代码在称为事件调度线程的特殊线程上运行。大多数调用 Swing 方法的代码也在该线程上运行。这是必要的,因为大多数 Swing 对象方法不是“线程安全的”:从多个线程调用它们有线程干扰或内存一致性错误的风险。1

问:那么,如果我们有一个 GUI 为什么要在单独的线程中启动它?
答:可能有比我更好的答案,但是如果您想从 EDT 更新 GUI(您这样做),那么您必须从 EDT 启动它。

问:为什么我们不能像任何其他线程一样启动线程?
答:见前面的回答。

问:为什么我们使用一些 invokeLater 以及为什么这个线程(EDT)在它准备好时开始执行请求。为什么它并不总是准备好?
答:EDT 可能还有一些其他的 AWT 事件需要处理。 invokeLater导致 doRun.run() 在 AWT 事件调度线程上异步执行。这将在处理完所有未决 AWT 事件后发生。当应用程序线程需要更新 GUI 时,应使用此方法。2

回答by Joonas Pulakka

You are actually startingthe counterthread from the EDT. If you called counter.start()after the invokeLaterblock, the counter would likely start to run beforethe GUI becomes visible. Now, because you're constructing the GUI in the EDT, the GUI wouldn't existwhen the counterstarts to update it. Luckily you seem to be forwarding the GUI updates to the EDT, which is correct, and since the EventQueue is a queue, the first update will happen after the GUI has been constructed, so there should be no reason why this wouldn't work. But what's the point of updating a GUI that may not be visible yet?

您实际上是从 EDT开始counter线程。如果counter.start()invokeLater块之后调用,计数器可能会在 GUI 可见之前开始运行。现在,因为您是在 EDT 中构建 GUI,所以在开始更新时GUI 将不存在counter。幸运的是,您似乎将 GUI 更新转发到 EDT,这是正确的,并且由于 EventQueue 是一个队列,第一次更新将在 GUI 构建后发生,因此应该没有理由为什么这不起作用。但是更新一个可能还不可见的 GUI 有什么意义呢?

回答by SyntaxT3rr0r

What is the EDT?

什么是 EDT?

It's a hacky workaround around the great many concurrency issues that the Swing API has ;)

这是围绕 Swing API 存在的大量并发问题的一种变通方法;)

Seriously, a lot of Swing components are not "thread safe" (some famous programmers went as far as calling Swing "thread hostile"). By having a unique thread where all updates are made to this thread-hostile components you're dodging a lot of potential concurrency issues. In addition to that, you're also guaranteed that it shall run the Runnablethat you pass through it using invokeLaterin a sequential order.

说真的,很多 Swing 组件都不是“线程安全的”(一些著名的程序员甚至称 Swing 为“线程敌对的”)。通过拥有一个独特的线程,在该线程中对这个线程敌对的组件进行所有更新,您可以避免许多潜在的并发问题。除此之外,您还可以保证它会按顺序运行Runnable您通过它使用invokeLater的内容。

Then some nitpicking:

然后一些吹毛求疵:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            showGUI();
            counter.start();
        }
    });
}

And then:

接着:

In the main method we also start the counter and the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right?

在 main 方法中,我们还启动了计数器,并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。对?

You don't really start the counter in the main method. You start the counter in the run()method of the anonymous Runnablethat is executed on the EDT. So you really startthe counter Threadfrom the EDT, not the main method. Then, because it's a separate Thread, it is not runon the EDT. But the counter definitely isstarted on the EDT, not in the Threadexecuting the main(...)method.

您并没有真正在 main 方法中启动计数器。您在 EDT 上执行的匿名程序的run()方法中启动计数器Runnable。所以你真的从 EDT开始计数器Thread,而不是主要方法。然后,因为它是一个单独的线程,所以它不在EDT 上运行。但是计数器肯定在 EDT 上启动的,而不是在Thread执行main(...)方法中。

It's nitpicking but still important seen the question I think.

这是吹毛求疵,但看到我认为的问题仍然很重要。

回答by Sandeep

This is simple, it is as follows

这个很简单,如下

Step 1 . Initial thread also called main thread is created.

步骤1 。创建初始线程也称为主线程。

Step 2. Create a runnable object and pass it to invokeLate().

步骤 2. 创建一个可运行对象并将其传递给 invokeLate()。

Step 3. This initialises the GUI but does not create the GUI.

步骤 3. 这会初始化 GUI,但不会创建 GUI。

Step 4. InvokeLater() schedules the created object for execution on EDT.

步骤 4. InvokeLater() 安排创建的对象在 EDT 上执行。

Step 5. GUI has been created.

步骤 5. GUI 已创建。

Step 6. All events occurring will be placed in EDT.

步骤 6. 所有发生的事件都将被放置在 EDT 中。