【问题标题】:When exactly is the Event Dispatch Thread started?事件调度线程究竟是什么时候开始的?
【发布时间】:2012-06-01 22:47:52
【问题描述】:

EDT 究竟是什么时候开始的?哪一行代码负责它?

我的猜测是“someSwingComponent.setVisible(true)”可以解决问题,但我不确定。

谢谢!

【问题讨论】:

  • 负责创建Event Dispatch Thread的方法是EventQueue.initDispatchThread,所以放个断点可能会给你一些启示。

标签: java swing event-dispatch-thread


【解决方案1】:

问:EDT 究竟是什么时候开始的?哪一行代码负责[f]它?

Swing 的内部工作是特定于 JVM 的。不同的 JVM 根据不同的标准启动事件调度线程 (EDT)。但总的来说:

EDT 在收到第一个AWTEvent 时启动。

下面的堆栈跟踪再次证实了这一点。以下面的main 方法为例。

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.setVisible(true);
}

在上面的例子中,负责启动 EDT 的代码行是frame.setVisible(true);

上述main 方法在两个不同的JVM 上执行。断点位于EventQueue.initDispatchThread。当断点被命中时,会记录以下堆栈跟踪。

AWT-AppKit线程上使用Mac的JDK:

EventQueue.initDispatchThread() line: 906   
EventQueue.wakeup(boolean) line: 1109   
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39  
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25  
Method.invoke(Object, Object...) line: 597  
SunToolkit.wakeupEventQueue(EventQueue, boolean) line: 348  
PostEventQueue.postEvent(AWTEvent) line: 2137   
SunToolkit.postEvent(AppContext, AWTEvent) line: 583    
SunToolkit.executeOnEventHandlerThread(PeerEvent) line: 654 
SunToolkit.executeOnEventHandlerThread(Object, Runnable) line: 631  
EventFactoryProxy.windowMoved(CWindow) line: 89 

main 线程上使用 Oracle 的 JDK for Windows:

java.awt.EventQueue.initDispatchThread() line: 861  
java.awt.EventQueue.postEventPrivate(java.awt.AWTEvent) line: 199   
java.awt.EventQueue.postEvent(java.awt.AWTEvent) line: 180  
javax.swing.RepaintManager.scheduleProcessingRunnable(sun.awt.AppContext) line: 1369    
javax.swing.RepaintManager.nativeAddDirtyRegion(sun.awt.AppContext, java.awt.Container, int, int, int, int) line: 548   
javax.swing.SwingPaintEventDispatcher.createPaintEvent(java.awt.Component, int, int, int, int) line: 45 
sun.awt.windows.WFramePeer(sun.awt.windows.WComponentPeer).postPaintIfNecessary(int, int, int, int) line: 741   
sun.awt.windows.WFramePeer(sun.awt.windows.WComponentPeer).handlePaint(int, int, int, int) line: 736    
sun.java2d.d3d.D3DScreenUpdateManager.repaintPeerTarget(sun.awt.windows.WComponentPeer) line: 274   
sun.java2d.d3d.D3DScreenUpdateManager.createScreenSurface(sun.awt.Win32GraphicsConfig, sun.awt.windows.WComponentPeer, int, boolean) line: 175  
...
sun.awt.windows.WToolkit.createFrame(java.awt.Frame) line: 383  
javax.swing.JFrame(java.awt.Frame).addNotify() line: 460    
javax.swing.JFrame(java.awt.Window).show() line: 859    
javax.swing.JFrame(java.awt.Component).show(boolean) line: 1584 
javax.swing.JFrame(java.awt.Component).setVisible(boolean) line: 1536   
javax.swing.JFrame(java.awt.Window).setVisible(boolean) line: 842   
Example.main(java.lang.String[]) line: 113

在 Mac 上,调用 PostEventQueue.postEvent(AWTEvent)。同样,在 Windows 上,调用 java.awt.EventQueue.postEvent(java.awt.AWTEvent)。两者最终都致电EventQueue.initDispatchThread


作为另一个例子,考虑以下main 方法:

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            System.out.println("Start!");
        }
    });
}

main线程上使用Mac的JDK:

EventQueue.initDispatchThread() line: 906 [local variables unavailable] 
EventQueue.postEventPrivate(AWTEvent) line: 227 
EventQueue.postEvent(AWTEvent) line: 208    
EventQueue.invokeLater(Runnable) line: 1048 
SwingUtilities.invokeLater(Runnable) line: 1267 
Example.main(String[]) line: 31 

main 线程上使用 Oracle 的 JDK for Windows:

java.awt.EventQueue.initDispatchThread() line: 861  
java.awt.EventQueue.postEventPrivate(java.awt.AWTEvent) line: 199   
java.awt.EventQueue.postEvent(java.awt.AWTEvent) line: 180  
java.awt.EventQueue.invokeLater(java.lang.Runnable) line: 999   
javax.swing.SwingUtilities.invokeLater(java.lang.Runnable) line: 1267

SwingUtilties.invokeLater 的调用负责启动EDT。再次调用EventQueue.postEvent(AWTEvent)


关于“我的猜测是“someSwingComponent.setVisible(true)”可以解决问题,但我不确定。”的想法。

不仅仅是任何对 someSwingComponent.setVisible(true) 的调用都会启动 EDT。例如,执行以下main 方法不会创建AWT-Event-Queue-0 线程:

public static void main(String[] args) {
    JLabel label = new JLabel();
    label.setVisible(true);
}

资源

当然,网上有很多关于 EDT 的资源。

【讨论】:

    【解决方案2】:
    • 对于任何启动 EDT 的 AWT / Swing 容器,您是正确的 setVisible,非安全方式

    • Initial Thread 是最安全的方式

    • 如果所有事件都在当前 EDT 中完成,则 isDispatchThread 返回 false

    • 如果 isDispatchThread 返回 false,那么任何 Swing 线程安全方法都可以在 EDT 中存活,最安全的方法是从 invokeLater() 调用该方法

    测试代码

    import java.awt.Color;
    import java.awt.EventQueue;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.concurrent.*;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.swing.*;
    
    public class IsThereEDT {
    
        private ScheduledExecutorService scheduler;
        private AccurateScheduledRunnable periodic;
        private ScheduledFuture<?> periodicMonitor;
        private int taskPeriod = 30;
        private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
        private Date dateRun;
        private JFrame frame1 = new JFrame("Frame 1");
    
        public IsThereEDT() {
            scheduler = Executors.newSingleThreadScheduledExecutor();
            periodic = new AccurateScheduledRunnable() {
    
                private final int ALLOWED_TARDINESS = 200;
                private int countRun = 0;
                private int countCalled = 0;
                private int maxCalled = 10;
    
                @Override
                public void run() {
                    countCalled++;
                    if (countCalled < maxCalled) {
                        if (countCalled % 3 == 0) {
                            SwingUtilities.invokeLater(new Runnable() {
    
                                @Override
                                public void run() {
                                    System.out.println("Push a new event to EDT");
                                    frame1.getContentPane().setBackground(Color.red);
                                    isThereReallyEDT();
                                }
                            });
                        } else {
                            if (this.getExecutionTime() < ALLOWED_TARDINESS) {
                                countRun++;
                                isThereReallyEDT(); // non on EDT
                            }
                        }
                    } else {
                        System.out.println("Terminating this madness");
                        System.exit(0);
                    }
                }
            };
            periodicMonitor = scheduler.scheduleAtFixedRate(periodic, 0, taskPeriod, TimeUnit.SECONDS);
            periodic.setThreadMonitor(periodicMonitor);
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    isThereReallyEDT();
                    frame1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame1.getContentPane().add(new JLabel("Hello in frame 1"));
                    frame1.pack();
                    frame1.setLocation(100, 100);
                    frame1.setVisible(true);
                }
            });
            try {
                Thread.sleep(500);
            } catch (InterruptedException ex) {
                Logger.getLogger(IsThereEDT.class.getName()).log(Level.SEVERE, null, ex);
            }
            SwingUtilities.invokeLater(new Runnable() {
    
                @Override
                public void run() {
                    JFrame frame2 = new JFrame("Frame 2");
                    frame2.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame2.getContentPane().add(new JLabel("Hello in frame 2"));
                    frame2.pack();
                    frame2.setLocation(200, 200);
                    frame2.setVisible(true);
                    isThereReallyEDT();
                }
            });
        }
    
        private void isThereReallyEDT() {
            dateRun = new java.util.Date();
            System.out.println("                         Time at : " + sdf.format(dateRun));
            if (EventQueue.isDispatchThread()) {
                System.out.println("EventQueue.isDispatchThread");
            } else {
                System.out.println("There isn't Live EventQueue.isDispatchThread, why any reason for that ");
            }
            if (SwingUtilities.isEventDispatchThread()) {
                System.out.println("SwingUtilities.isEventDispatchThread");
            } else {
                System.out.println("There isn't Live SwingUtilities.isEventDispatchThread, why any reason for that ");
            }
            System.out.println();
        }
    
        public static void main(String[] args) {
            IsThereEDT isdt = new IsThereEDT();
        }
    }
    
    abstract class AccurateScheduledRunnable implements Runnable {
    
        private ScheduledFuture<?> thisThreadsMonitor;
    
        public void setThreadMonitor(ScheduledFuture<?> monitor) {
            this.thisThreadsMonitor = monitor;
        }
    
        protected long getExecutionTime() {
            long delay = -1 * thisThreadsMonitor.getDelay(TimeUnit.MILLISECONDS);
            return delay;
        }
    }
    

    【讨论】:

      【解决方案3】:

      编辑:你们是对的; EDT 不会在启动时直接启动。我做了一些调试,这就是我发现的:

      每当组件通过调用 Toolkit.getEventQueue() 请求访问事件队列时,都会延迟启动事件调度线程。这可以在调用 Component.show() 时完成(与 Component.setVisible() 相同),但也有其他调用可以触发此初始化,如 Component.repaint()。一旦获得对事件队列的引用,就可以使用 EventQueue.postEvent() 将作业添加到其中。此方法检查 EDT 是否存在,如果不存在,则使用 initDispatchThread() 创建它。

      阻止它启动的唯一方法是使用“-Djava.awt.headless=true”标志以无头模式启动 JVM(这会同时禁用 AWT)。但这基本上是您可以与之进行的唯一低级交互。

      组件的 setVisible 方法应始终在 EDT 上调用(就像您对 Swing / AWT 组件所做的任何其他修改一样)。您通过告诉 Java 在 EDT 上执行代码来使用 EDT。最简单的方法是使用 SwingUtilities.invokeLater()。这会安排您的 Thread(您的 Runnable 实现)从 EDT 执行。这是您作为开发人员应该与 EDT 进行的唯一一种交互。您不应该与 EDT 进行任何类型的低级别交互,例如暂停或中止线程。

      【讨论】:

      • 我不相信这句话是真的:“事件调度线程是在 JVM 启动期间启动的。”在应用程序启动时,AWT-EventQueue-0 线程还不存在。彼得是对的;通常调用JFrame.setVisible 或类似的调用会启动此线程。
      猜你喜欢
      • 2014-09-09
      • 2023-02-25
      • 1970-01-01
      • 1970-01-01
      • 2015-10-12
      • 2011-08-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多