• Swing线程之SwingUtilities.invokeLater 解释


    在官方的文章里:http://docs.oracle.com/javase/tutorial/uiswing/painting/step1.html 告诉我们要创建一个gui。

    All Graphical User Interfaces require some kind of main application frame in which to display. In Swing, this is an instance ofjavax.swing.JFrame. Therefore, our first step is to instantiate this class and make sure that everything works as expected. Note that when programming in Swing, your GUI creation code should be placed on the Event Dispatch Thread (EDT). This will prevent potential race conditions that could lead to deadlock. The following code listing shows how this is done.

    import javax.swing.SwingUtilities;
    import javax.swing.JFrame;
    
    public class SwingPaintDemo1 {
        
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    createAndShowGUI();
                }
            });
        }
        
        private static void createAndShowGUI() {
            System.out.println("Created GUI on EDT? "+
                    SwingUtilities.isEventDispatchThread());
            JFrame f = new JFrame("Swing Paint Demo");
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            f.setSize(250,250);
            f.setVisible(true);
        }
    }

    This creates the frame, sets its title, and makes everything visible. We have used the SwingUtilities helper class to construct this GUI on the Event Dispatch Thread. Note that by default, a JFrame does not exit the application when the user clicks its "close" button. We provide this behavior by invoking the setDefaultCloseOperation method, passing in the appropriate argument. Also, we are explicity setting the frame's size to 250 x 250 pixels. This step will not be necessary once we start adding components to the frame.

    事件分发线程:

    Swing event-handling and painting code executes in a single thread, called the event-dispatching thread. This ensures that each event handler finishes executing before the next one executes and that painting isn't interrupted by events. To avoid the possibility of deadlock, you must take extreme care that Swing components and models are created, modified, and queriedonly from the event-dispatching thread.


    Note: We used to say that you could create the GUI on the main thread as long as you didn't modify components that had already been realized. [PENDING: The following red text belongs in a footnote.] Realized means that the component has been painted onscreen, or is ready to be painted. The methods setVisible(true) and pack cause a window to be realized, which in turn causes the components it contains to be realized. While this worked for most applications, in certain situations it could cause problems. Out of all the demos in the Swing Tutorial, we encountered a problem only in ComponentEventDemo. In that case, sometimes when you launched the demo, it would not come up because it would deadlock when updating the text area if the text area had not yet been realized, while other times it would come up without incident.

    To avoid the possibility of thread problems, we recommend that you use invokeLater to create the GUI on the event-dispatching thread for all new applications. If you have old programs that are working fine they are probably OK; however you might want to convert them when it's convenient to do so.


    You have probably noticed that most of the tutorial's demos use a standardized main method that calls the SwingUtilities methodinvokeLater to ensure that the GUI is created on the event-dispatching thread. Here is an example of the main method from the FocusConceptsDemo example. We have also included the source for createAndShowGUI — a private, static method called by eachmain method where the creation of the GUI is handled.

    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event-dispatching thread.
     */
    private static void createAndShowGUI() {
        //Make sure we have nice window decorations.
        JFrame.setDefaultLookAndFeelDecorated(true);
    
        //Create and set up the window.
        frame = new JFrame("FocusConceptsDemo");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        //Create and set up the content pane.
        JComponent newContentPane = new FocusConceptsDemo();
        newContentPane.setOpaque(true); //content panes must be opaque
        frame.setContentPane(newContentPane);
    
        //Display the window.
        frame.pack();
        frame.setVisible(true);
    }
    
    public static void main(String[] args) {
        //Schedule a job for the event-dispatching thread:
        //creating and showing this application's GUI.
        javax.swing.SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    Using the invokeLater Method

    You can call invokeLater from any thread to request the event-dispatching thread to run certain code. You must put this code in  the run method of a Runnable object and specify the Runnable object as the argument to invokeLater. The invokeLater method returns immediately, without waiting for the event-dispatching thread to execute the code. Here's an example of usinginvokeLater:

    Runnable updateAComponent = new Runnable() {
        public void run() { component.doSomething(); }
    };
    SwingUtilities.invokeLater(updateAComponent);
    

    invokeLater必须放在run()方法体内。

    Using the invokeAndWait Method

    The invokeAndWait method is just like invokeLater, except that invokeAndWait doesn't return until the event-dispatching thread has executed the specified code. Whenever possible, you should use invokeLater instead of invokeAndWait — it is very easy to cause a deadlock using invokeAndWait. If you use invokeAndWait, make sure that the thread that calls invokeAndWait does not hold any locks that other threads might need while the call is occurring.

    Here's an example of using invokeAndWait:

    void showHelloThereDialog() throws Exception {
        Runnable showModalDialog = new Runnable() {
            public void run() {
                JOptionPane.showMessageDialog(myMainFrame,
                                              "Hello There");
            }
        };
        SwingUtilities.invokeAndWait(showModalDialog);
    }
    

    Similarly, a thread that needs access to GUI state, such as the contents of a pair of text fields, might have the following code:

    void printTextField() throws Exception {
        final String[] myStrings = new String[2];
    
        Runnable getTextFieldText = new Runnable() {
            public void run() {
                myStrings[0] = textField0.getText();
                myStrings[1] = textField1.getText();
            }
        };
        SwingUtilities.invokeAndWait(getTextFieldText);
    
        System.out.println(myStrings[0] + " " + myStrings[1]);
    }

    Using Threads to Improve Performance

    When properly used, threads can be a powerful tool. However, you must proceed with caution when using threads in a Swing program. Despite the dangers, threads can be invaluable. You can use them to improve your program's perceived performance. And sometimes threads can simplify a program's code or architecture. Here are some typical situations where threads are used:
    • To move a time-consuming initialization task out of the main thread, so that the GUI comes up faster. Examples of time-consuming tasks include making extensive calculations and blocking for network or disk I/O (loading images, for example).
    • To move a time-consuming task out of the event-dispatching thread, so that the GUI remains responsive.
    • To perform an operation repeatedly, usually with some predetermined period of time between operations.
    • To wait for messages from other programs.

    If you need to create a thread, you can avoid some common pitfalls by implementing the thread with a utility class such asSwingWorker or one of the Timer classes.SwingWorker object creates a thread to execute a time-consuming operation. After the operation is finished, SwingWorker gives you the option of executing some additional code in the event-dispatching thread. Timers are useful for performing a task either repeatedly or after a specified delay. If you need to implement your own threads, you can find information on doing so in Concurrency.

    You can use several techniques to make multi-threaded Swing programs work well:

    • If you need to update a component but your code isn't executing in an event listener, use one of the two SwingUtilitiesmethods: invokeLater (preferred) or invokeAndWait.
    • If you aren't sure whether your code is executing in an event listener, then you should analyze your program's code and document which thread each method is (and can be) called from. Failing that, you can use theSwingUtilities.isEventDispatchThread() method, which returns true if your code is executing in the event-dispatching thread. You can safely call invokeLater from any thread, but invokeAndWait throws an exception if it's called from the event-dispatching thread.
    • If you need to update a component after a delay (whether or not your code is currently executing in an event listener), use a timer to do so.
    • If you need to update a component at a regular interval, use a timer.
    For information and examples of using timers, see How to Use Swing Timers.

    Using the SwingWorker Class


    Note:  The implementation of the SwingWorker class has been updated twice, most recently in February 2000. The first update (in January 1999) allowed programs to safely interrupt the worker thread. The most recent update (called "SwingWorker 3") was to fix a subtle threading bug that could cause a NullPointerException.
    The SwingWorker class is implemented in SwingWorker.java, which is not in the Swing release. To use the SwingWorker class, you first create a subclass of it. The subclass must implement the construct method so that it contains the code to perform your lengthy operation. When you instantiate your SwingWorker subclass, the SwingWorker creates a thread but does not (as of SwingWorker 3) start it. You then invoke start on your SwingWorker object to start the thread, which then calls your constructmethod.

    Here is an example of using a SwingWorker to move a time-consuming task from an action event listener into a background thread, so that the GUI remains responsive.

    //OLD CODE:
    public void actionPerformed(ActionEvent e) {
        ...
        //...code that might take a while to execute is here...
        ...
    }
    
    //BETTER CODE:
    public void actionPerformed(ActionEvent e) {
        ...
        final SwingWorker worker = new SwingWorker() {
            public Object construct() {
                //...code that might take a while to execute is here...
                return someValue;
            }
        };
        worker.start();  //required for SwingWorker 3
        ...
    }
    

    The value that construct returns can be any object. If you need to get the value, you can do so by invoking the get method on your SwingWorker object. Be careful about using get. Because it blocks, it can cause deadlock. If necessary, you can interrupt the thread (causing get to return) by invoking interrupt on the SwingWorker.

    If you need to update the GUI when the time-consuming operation completes, you can do so either by using get (which is dangerous, as we noted) or by overriding the finished method in your SwingWorker subclass. The finished method runs after theconstruct method returns. Because the finished method executes in the event-dispatching thread, you can safely use it to update Swing components. Of course, you shouldn't put time-consuming operations in your finished implementation.

    The following example of implementing finished is taken from IconDemoApplet.java. For a full discussion of this applet, including how it improves perceived performance by using background threads to load images, see How to Use Icons.

    public void actionPerformed(ActionEvent e) {
        ...
        if (icon == null) {     //haven't viewed this photo before
            loadImage(imagedir + pic.filename, current);
        } else {
            updatePhotograph(current, pic);
        }
    }
    ...
    //Load an image in a separate thread.
    private void loadImage(final String imagePath, final int index) {
        final SwingWorker worker = new SwingWorker() {
            ImageIcon icon = null;
    
            public Object construct() {
                icon = new ImageIcon(getURL(imagePath));
                return icon; //return value not used by this program
            }
    
            //Runs on the event-dispatching thread.
            public void finished() {
                Photo pic = (Photo)pictures.elementAt(index);
                pic.setIcon(icon);
                if (index == current)
                    updatePhotograph(index, pic);
            }
        };
        worker.start(); 
    }
    

    For more examples of using SwingWorker, go to How to Monitor Progress. Also, TumbleItem.java, which is discussed in How to Make Applets, uses both a SwingWorker and a Timer.

    via:http://www.ime.uerj.br/javatutor/uiswing/misc/threads.html

     

    官方解释:

    public static void invokeLater(Runnable doRun)
    Causes 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. In the following example the invokeLater call queues the Runnable object doHelloWorld on the event dispatching thread and then prints a message.
     Runnable doHelloWorld = new Runnable() {
         public void run() {
             System.out.println("Hello World on " + Thread.currentThread());
         }
     };
    
     SwingUtilities.invokeLater(doHelloWorld);
     System.out.println("This might well be displayed before the other message.");
     
    If invokeLater is called from the event dispatching thread -- for example, from a JButton's ActionListener -- the doRun.run() will still be deferred until all pending events have been processed. Note that if the doRun.run() throws an uncaught exception the event dispatching thread will unwind (not the current thread).

    Additional documentation and examples for this method can be found in How to Use Threads, in The Java Tutorial.

    As of 1.3 this method is just a cover for java.awt.EventQueue.invokeLater().

    Unlike the rest of Swing, this method can be invoked from any thread.

    See Also:
    invokeAndWait(java.lang.Runnable)
    SwingUtilities
    A collection of utility methods for Swing
     
    csdn的一篇文章:

    现在我们要做一个简单的界面。

    包括一个进度条、一个输入框、开始和停止按钮。

    需要实现的功能是:

    当点击开始按钮,则更新进度条,并且在输入框内把完成的百分比输出(这里只做例子,没有真正去做某个工作)。

    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JProgressBar;
    import javax.swing.JTextField;
    public class SwingThreadTest1 extends JFrame {
        private static final long serialVersionUID = 1L;
        private static final String STR = "Completed : ";
        private JProgressBar progressBar = new JProgressBar();
        private JTextField text = new JTextField(10);
        private JButton start = new JButton("Start");
        private JButton end = new JButton("End");
        private boolean flag = false;
        private int count = 0;
        public SwingThreadTest1() {
            this.setLayout(new FlowLayout());
            add(progressBar);
            text.setEditable(false);
            add(text);
            add(start);
            add(end);
            start.addActionListener(new Start());
            end.addActionListener(new End());
        }
            
        private void go() {
            while (count < 100) {
                try {
                    Thread.sleep(100);//这里比作要完成的某个耗时的工作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                             //更新进度条和输入框
                if (flag) {
                    count++;
                    progressBar.setValue(count);
                    text.setText(STR + String.valueOf(count) + "%");
                }
            }
        }
        private class Start implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                flag = true;//设置开始更新的标志
                go();//开始工作
            }
        }
        private class End implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                flag = false;//停止
            }
        }
        public static void main(String[] args) {
            SwingThreadTest1 fg = new SwingThreadTest1();
            fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            fg.setSize(300, 100);
            fg.setVisible(true);
        }
    }

    运行代码发现,

    现象1当点击了开始按钮,画面就卡住了。按钮不能点击,进度条没有被更新,输入框上也没有任何信息。

    原因分析:Swing是线程不安全的,是单线程的设计,所以只能从事件派发线程访问将要在屏幕上绘制的Swing组件。ActionListener的actionPerformed方法是在事件派发线程中调用执行的,而点击了开始按钮后,执行了go()方法,在go()里,虽然也去执行了更新组件的方法

    progressBar.setValue(count);

    text.setText(STR + String.valueOf(count) + "%"); 还可以参看:http://www.cnblogs.com/youxin/archive/2012/04/20/2459557.html

    但由于go()方法直到循环结束,它并没有返回,所以更新组件的操作一直没有被执行,这就造成了画面卡住的现象。

    现象2过了一段时间(go方法里的循环结束了)后,画面又可以操作,并且进度条被更新,输入框也出现了我们想看到的信息。

    原因分析:通过在现象1的分析,很容易联想到,当go()方法返回了,则其他的线程(更新组件)可以被派发了,所以画面上的组件被更新了。

    为了让画面不会卡住,我们来修改代码,将耗时的工作放在一个线程里去做。

    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JProgressBar;
    import javax.swing.JTextField;
    public class SwingThreadTest2 extends JFrame {
        private static final long serialVersionUID = 1L;
        private static final String STR = "Completed : ";
        private JProgressBar progressBar = new JProgressBar();
        private JTextField text = new JTextField(10);
        private JButton start = new JButton("Start");
        private JButton end = new JButton("End");
        private boolean flag = false;
        private int count = 0;
        
        GoThread t = null;
        public SwingThreadTest2() {
            this.setLayout(new FlowLayout());
            add(progressBar);
            text.setEditable(false);
            add(text);
            add(start);
            add(end);
            start.addActionListener(new Start());
            end.addActionListener(new End());
        }
        private void go() {
            while (count < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (flag) {
                    count++;
                    System.out.println(count);
                    progressBar.setValue(count);
                    text.setText(STR + String.valueOf(count) + "%");
                }
            }
        }
        private class Start implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                flag = true;
                if(t == null){
                    t = new GoThread();
                    t.start();
                }
            }
        }
        //执行复杂工作,然后更新组件的线程
        class GoThread extends Thread{
            public void run() {
                //do something...
                go();
            }
        }
        private class End implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                flag = false;
            }
        }
        public static void main(String[] args) {
            SwingThreadTest2 fg = new SwingThreadTest2();
            fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            fg.setSize(300, 100);
            fg.setVisible(true);
        }
    }

    我们执行了程序,结果和我们想要的一样,画面不会卡住了。

    那这个程序是否没有问题了呢?

    我们自定义了一个线程GoThread,在这里我们完成了那些耗时的工作,可以看作是“工作线程”,

    而对于组件的更新,我们也放在了“工作线程”里完成了。

    在这里,在事件派发线程以外的线程里设置进度条,是一个危险的操作,运行是不正常的。(对于输入框组件的更新是安全的。)

     只有从事件派发线程才能更新组件,根据这个原则,我们来修改我们现有代码。

    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JProgressBar;
    import javax.swing.JTextField;
    import javax.swing.SwingUtilities;
    public class SwingThreadTest3 extends JFrame {
        private static final long serialVersionUID = 1L;
        private static final String STR = "Completed : ";
        private JProgressBar progressBar = new JProgressBar();
        private JTextField text = new JTextField(10);
        private JButton start = new JButton("Start");
        private JButton end = new JButton("End");
        private boolean flag = false;
        private int count = 0;
        
        private GoThread t = null;
        
        private Runnable run = null;//更新组件的线程
        public SwingThreadTest3() {
            this.setLayout(new FlowLayout());
            add(progressBar);
            text.setEditable(false);
            add(text);
            add(start);
            add(end);
            start.addActionListener(new Start());
            end.addActionListener(new End());
            
            run = new Runnable(){//实例化更新组件的线程
                public void run() {
                    progressBar.setValue(count);
                    text.setText(STR + String.valueOf(count) + "%");
                }
            };
        }
        private void go() {
            while (count < 100) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (flag) {
                    count++;
                    SwingUtilities.invokeLater(run);//将对象排到事件派发线程的队列中
                }
            }
        }
        private class Start implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                flag = true;
                if(t == null){
                    t = new GoThread();
                    t.start();
                }
            }
        }
        
        class GoThread extends Thread{
            public void run() {
                //do something...
                go();
            }
        }
        private class End implements ActionListener {
            public void actionPerformed(ActionEvent e) {
                flag = false;
            }
        }
        public static void main(String[] args) {
            SwingThreadTest3 fg = new SwingThreadTest3();
            fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            fg.setSize(300, 100);
            fg.setVisible(true);
        }
    }

    解释:SwingUtilities.invokeLater()方法使事件派发线程上的可运行对象排队。当可运行对象排在事件派发队列的队首时,就调用其run方法。其效果是允许事件派发线程调用另一个线程中的任意一个代码块

    还有一个方法SwingUtilities.invokeAndWait()方法,它也可以使事件派发线程上的可运行对象排队。

    他们的不同之处在于:SwingUtilities.invokeLater()在把可运行的对象放入队列后就返回,而SwingUtilities.invokeAndWait()一直等待知道已启动了可运行的run方法才返回。如果一个操作在另外一个操作执行之前必须从一个组件获得信息,则应使用SwingUtilities.invokeAndWait()方法。via:http://blog.csdn.net/bzwm/article/details/3895381

     

    http://www.ime.uerj.br/javatutor/uiswing/misc/threads.html

    http://docs.oracle.com/javase/7/docs/api/javax/swing/SwingUtilities.html#invokeLater(java.lang.Runnable)

    http://docs.oracle.com/javase/tutorial/essential/concurrency

     

     

  • 相关阅读:
    Java基础加强-内部类及代理
    金额货币转换函数
    SAP ABAP exporting list to memory ...SUBMIT 程序传输屏幕参数
    得到时间戳的函数
    alv行可编辑时带出描述
    ALV编辑行内容有改变时候操作
    ALV判断修改后是否有不合法数据,有则选中错误行,高亮度显示。
    数据字典的QUAN DEC类型与ABAP P型转换
    屏幕编程 F4的帮组用法
    read table 时关键字TRANSPORTING NO FIELDS的用法
  • 原文地址:https://www.cnblogs.com/youxin/p/2706471.html
Copyright © 2020-2023  润新知