Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/user-interface/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 事件分派线程是如何工作的?_Java_User Interface_Events_Multithreading_Invoke - Fatal编程技术网

Java 事件分派线程是如何工作的?

Java 事件分派线程是如何工作的?,java,user-interface,events,multithreading,invoke,Java,User Interface,Events,Multithreading,Invoke,在人们的帮助下,我能够得到以下简单GUI倒计时的工作代码(只显示一个倒计时秒的窗口)。这段代码的主要问题是invokeLater之类的东西 据我所知,invokeLater,它向事件调度线程(EDT)发送一个任务,然后EDT“可以”时执行该任务(不管这意味着什么)是这样吗? import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class Countdown

在人们的帮助下,我能够得到以下简单GUI倒计时的工作代码(只显示一个倒计时秒的窗口)。这段代码的主要问题是
invokeLater
之类的东西

据我所知,
invokeLater
,它向事件调度线程(EDT)发送一个任务,然后EDT“可以”时执行该任务(不管这意味着什么)是这样吗?

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();
            }
        });
    }

}
据我所知,代码的工作原理如下:

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

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

  • 计数器在单独的线程中执行,并定期调用
    updateGUI
    <代码>更新GUI
    应该更新GUI。图形用户界面在EDT中工作。因此,
    updateGUI
    也应该在EDT中执行。这就是为什么
    updateGUI
    的代码包含在
    invokeLater
    中的原因。是这样吗

  • 我不清楚的是为什么我们从EDT调用
    计数器。无论如何,它不会在EDT中执行。它立即启动,一个新线程和
    计数器在那里执行。那么,为什么我们不能在
    调用器
    块之后调用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();
                }
            });
        }
    
    }
    

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

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

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                showGUI();
            }
        });
        counter.start();
    }
    
    您不能这样做的原因是因为调度程序不保证。。。仅仅因为您调用了
    showGUI()
    ,然后又调用了
    counter.start()
    ,并不意味着
    showGUI()
    中的代码将在
    计数器的run方法中的代码之前执行

    这样想:

    • invokeLater启动一个线程,该线程在EDT上调度一个异步事件,该事件的任务是创建
      JLabel
    • 计数器是一个独立的线程,它依赖于
      JLabel
      的存在,因此它可以调用
      label.setText(“您有”+i+“秒”)
    现在您有了竞争条件:
    JLabel
    必须在
    计数器
    线程启动之前创建,如果它不是在计数器线程启动之前创建的,那么您的计数器线程将在未初始化的对象上调用
    setText

    为了确保消除争用条件,我们必须保证执行顺序和单向
    ,以保证在同一线程上顺序执行
    showGUI()
    counter.start()

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                showGUI();
                counter.start();
            }
        });
    }
    
    现在
    showGUI()
    计数器.start()从同一线程执行,因此
    JLabel
    将在
    计数器启动之前创建

    更新:

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

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

    Q:为什么我们不能像其他线程一样启动线程?
    A:参见前面的答案

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

    什么是EDT

    这是一种针对Swing API存在的大量并发问题的黑客解决方法;)

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

    然后是吹毛求疵:

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

    在main方法中,我们还启动 柜台和柜台 构造)在另一个 线程(因此它不在事件中