事件派发线程如何工作?

Rom*_*man 12 java events user-interface multithreading invoke

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

据我所知invokeLater,它向事件调度线程(EDT)发送任务,然后EDT在"可以"(无论这意味着什么)时执行该任务.是对的吗?

根据我的理解,代码的工作原理如下:

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

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

  3. counter在一个单独的线程中执行,并定期调用updateGUI.updateGUI应该更新GUI.GUI正在EDT中运行.所以,updateGUI也应该在EDT中执行.这就是为什么该代码的原因updateGUI被封闭在invokeLater.是对的吗?

我不清楚的是为什么我们打电话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();
            }
        });
    }

}
Run Code Online (Sandbox Code Playgroud)

Kir*_*ril 16

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

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

你不能这样做的原因是因为调度程序不保证...只是因为你调用showGUI()然后你调用counter.start()并不意味着代码showGUI()将在run方法中的代码之前执行counter.

想一想:

  • invokeLater的启动一个线程和线程的调度,其任务是在美国东部时间异步事件产生JLabel.
  • 计数器是一个单独的线程,依赖于JLabel存在,因此它可以调用label.setText("You have " + i + " seconds.");

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

为了确保消除竞争条件,我们必须保证执行的顺序,并保证一种方法是在同一个线程上执行showGUI()counter.start()顺序执行:

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

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

更新:

问: 我不明白这个帖子有什么特别之处.
答: Swing事件处理代码在称为事件派发线程的特殊线程上运行.大多数调用Swing方法的代码也在这个线程上运行.这是必要的,因为大多数Swing对象方法都不是"线程安全的":从多个线程调用它们会冒线程干扰或内存一致性错误.1

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

问: 为什么我们不能像其他任何线程一样启动线程?
答:见上一个答案.

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