java updating UI components from another thread

2019-03-01 12:18发布

I found many answers about my question, but I still don't understand why my application does not throw any exceptions. I created a new java form application in NetBeans 8. My form is created and displayed in main method like this:

public static void main(String args[])
    {
        /* Set the Nimbus look and feel */
        //<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
        /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel.
         * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html 
         */
        try
        {
            for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels())
            {
                if ("Nimbus".equals(info.getName()))
                {
                    javax.swing.UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        }
        catch (ClassNotFoundException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (InstantiationException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (IllegalAccessException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        catch (javax.swing.UnsupportedLookAndFeelException ex)
        {
            java.util.logging.Logger.getLogger(MainForm.class.getName()).log(java.util.logging.Level.SEVERE, null, ex);
        }
        //</editor-fold>

        /* Create and display the form */
        java.awt.EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                new MainForm().setVisible(true);     
            }
        });
    }

So, this new Runnable creates new MainForm and sets it visible.

Then, in my code I start new threads which updates some jButtons and jTextFields. Code below:

private void updateUI() {
        updateUIThread = new Thread(() ->
        { 
            while (true) {
                try {
                    jtfIP.setEnabled(!Start && !autoRec);
                    jtfPort.setEnabled(!Start && !autoRec);
                    jtfSlaveID.setEnabled(!Start && !autoRec);
                    jtfTimeout.setEnabled(!Start && !autoRec);
                    jtfReqInterval.setEnabled(!Start && !autoRec);
                    jCheckBox1.setEnabled(!Start && !autoRec);
                    jCBReconnect.setEnabled(!Start && !autoRec);

                    if (db != null) {
                        if (!db.getIsOpen()) {
                            jPBD.setBackground(Color.RED);
                            jPBD.setForeground(Color.WHITE);
                            jPBD.setText("ER");
                        } else {
                            jPBD.setBackground(Color.GREEN);
                            jPBD.setForeground(Color.BLACK);
                            jPBD.setText("OK ");
                        }
                    } else {
                        jPBD.setBackground(Color.RED);
                        jPBD.setForeground(Color.WHITE);
                        jPBD.setText(" ER ");
                    }


                    if (autoRec){
                        jbtnConnect.setText("Auto");
                        if (Start && Connected) {
                            jbtnConnect.setForeground(Color.BLACK);
                            jbtnConnect.setBackground(Color.GREEN);
                        } else {       
                            jbtnConnect.setForeground(Color.WHITE);
                            jbtnConnect.setBackground(Color.RED);
                        }
                    } else {
                        if (Start) {
                            jbtnConnect.setText("Disconnect");
                            jbtnConnect.setForeground(Color.BLACK);
                            jbtnConnect.setBackground(Color.GREEN);

                        } else {
                            jbtnConnect.setText("Connect");
                            jbtnConnect.setForeground(Color.WHITE);
                            jbtnConnect.setBackground(Color.RED);
                        }
                    }

                    jtfErroriCitire.setText(String.valueOf(totalErrors));

                    try
                    {
                        Thread.sleep(300);
                        jPanel4.repaint(1);
                    }
                    catch (InterruptedException ex)
                    {
                        Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                catch (Exception ex) {
                    Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
        updateUIThread.start();
    }

And there are other threads started like this above and where I get different values which are updated in the above thread.

My question is why my code does not throw any exception regarding UI elements which are updated from another thread? I did NOT use SwingUtilities.invokeLater(new Runnable() { //code here }); And my code executes perfectly...

Thank you!

3条回答
乱世女痞
2楼-- · 2019-03-01 12:25

Swing is not thread safe and is single threaded. You should never update UI components from outside the Event Dispatching Thread, equally, you should never run long running processes or blocking code within the EDT, as this will prevent it from processing new events within the event queue, causing your app to look like it's hung...because it has...

Take a look at Concurrency in Swing for more details.

After scratching my head for a while, I realised, the simple solution would be to just use javax.swing.Timer

You want to repeat the update at a regular interval (300 milliseconds) and update the UI, perfect, the Swing Timer is capable of scheduling updates at regular intervals and executes it call back within the context of the EDT!

It also has the ability to consolidate repeated calls. This means, if there is already a "timer" action in the event queue, the timer will not generate a new one, preventing from flooding the EDT and cause possible performance issues...

javax.swing.Timer timer = new Timer(300, new ActionListener() {
    public void actionPerformed(ActionEvent evt) {    
        jtfIP.setEnabled(!Start && !autoRec);
        jtfPort.setEnabled(!Start && !autoRec);
        jtfSlaveID.setEnabled(!Start && !autoRec);
        jtfTimeout.setEnabled(!Start && !autoRec);
        jtfReqInterval.setEnabled(!Start && !autoRec);
        jCheckBox1.setEnabled(!Start && !autoRec);
        jCBReconnect.setEnabled(!Start && !autoRec);

        if (db != null) {
            if (!db.getIsOpen()) {
                jPBD.setBackground(Color.RED);
                jPBD.setForeground(Color.WHITE);
                jPBD.setText("ER");
            } else {
                jPBD.setBackground(Color.GREEN);
                jPBD.setForeground(Color.BLACK);
                jPBD.setText("OK ");
            }
        } else {
            jPBD.setBackground(Color.RED);
            jPBD.setForeground(Color.WHITE);
            jPBD.setText(" ER ");
        }


        if (autoRec){
            jbtnConnect.setText("Auto");
            if (Start && Connected) {
                jbtnConnect.setForeground(Color.BLACK);
                jbtnConnect.setBackground(Color.GREEN);
            } else {       
                jbtnConnect.setForeground(Color.WHITE);
                jbtnConnect.setBackground(Color.RED);
            }
        } else {
            if (Start) {
                jbtnConnect.setText("Disconnect");
                jbtnConnect.setForeground(Color.BLACK);
                jbtnConnect.setBackground(Color.GREEN);

            } else {
                jbtnConnect.setText("Connect");
                jbtnConnect.setForeground(Color.WHITE);
                jbtnConnect.setBackground(Color.RED);
            }
        }

        jtfErroriCitire.setText(String.valueOf(totalErrors));
    }
});
timer.start();

See How to use Swing Timers for more details

查看更多
时光不老,我们不散
3楼-- · 2019-03-01 12:33

And I did this.

private void updateUI() {
    updateUIThread = new Thread(() ->
    { 
        while (true) {
            try {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        jtfIP.setEnabled(!Start && !autoRec);
                        jtfPort.setEnabled(!Start && !autoRec);
                        jtfSlaveID.setEnabled(!Start && !autoRec);
                        jtfTimeout.setEnabled(!Start && !autoRec);
                        jtfReqInterval.setEnabled(!Start && !autoRec);
                        jCheckBox1.setEnabled(!Start && !autoRec);
                        jCBReconnect.setEnabled(!Start && !autoRec);

                        if (db != null) {
                            if (!db.getIsOpen()) {
                                jPBD.setBackground(Color.RED);
                                jPBD.setForeground(Color.WHITE);
                                jPBD.setText("ER");
                            } else {
                                jPBD.setBackground(Color.GREEN);
                                jPBD.setForeground(Color.BLACK);
                                jPBD.setText("OK ");
                            }
                        } else {
                            jPBD.setBackground(Color.RED);
                            jPBD.setForeground(Color.WHITE);
                            jPBD.setText(" ER ");
                        }


                        if (autoRec){
                            jbtnConnect.setText("Auto");
                            if (Start && Connected) {
                                jbtnConnect.setForeground(Color.BLACK);
                                jbtnConnect.setBackground(Color.GREEN);
                            } else {       
                                jbtnConnect.setForeground(Color.WHITE);
                                jbtnConnect.setBackground(Color.RED);
                            }
                        } else {
                            if (Start) {
                                jbtnConnect.setText("Disconnect");
                                jbtnConnect.setForeground(Color.BLACK);
                                jbtnConnect.setBackground(Color.GREEN);

                            } else {
                                jbtnConnect.setText("Connect");
                                jbtnConnect.setForeground(Color.WHITE);
                                jbtnConnect.setBackground(Color.RED);
                            }
                        }

                        jtfErroriCitire.setText(String.valueOf(totalErrors));
                    }
                });
                try
                {
                    Thread.sleep(300);
                    jPanel4.repaint(1);
                }
                catch (InterruptedException ex)
                {
                    Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
            catch (Exception ex) {
                Logger.getLogger(MainForm.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    });
    updateUIThread.start();
}

I put my update UI code in run method of invokeLater. updateUI() it's called when application starts.

查看更多
小情绪 Triste *
4楼-- · 2019-03-01 12:38

Swing says you shouldn't update components from outside the Swing Event Dispatch Thread, but it doesn't enforce that. It's just not at all practical to check to see which Thread every single call is coming from.

Also, because of the nature of the problems that tend to result from threading issues (just generally), you should not expect Exceptions to always be thrown when you have a bug in multi-threaded code. This is because threading issues frequently result in deadlock or memory consistency errors, which are in most cases not recoverable (usually the whole JVM just crashes).

查看更多
登录 后发表回答