簡體   English   中英

Java Swing中的奇怪並發問題

[英]Strange Concurrency Issue in Java Swing

我有一個Jframe,它是我的應用程序窗口(以下代碼中的appFrame),它包含大量邏輯並需要大約1秒左右才能加載。 在此期間,我想向我的用戶展示一個非常好的加載框架(initFrame)。 但是,當我運行此代碼時,initFrame會出現,但是JLabel上的文本不會立即出現 - 它實際上在短暫的時刻根本不會出現,直到加載應用程序框架。

如果我注釋掉所有appFrame,並且只啟動initFrame,文本會立即加載,根本沒有等待時間。 為什么會這樣? 這可能是並發問題嗎?

public static void main(String[] args) {
    SwingUtilities.invokeLater(new Runnable() { //as per best practice for concurrency in swing - see http://docs.oracle.com/javase/tutorial/uiswing/concurrency/
        @Override
        public void run() {
            final JFrame initFrame = new InitFrame();
            initFrame.setVisible(true);
            final AppFrame appFrame = new AppFrame();
            appFrame.setVisible(true);
            initFrame.setVisible(false);
            initFrame.dispose();
        }
    });

}

我會將幀的創建分成兩個線程。 第一個,初始化InitFrame 運行此線程並在InitFrame對象上調用isShowing() 當它返回true時,運行第二個線程來初始化並顯示AppFrame

這將強制在兩個幀的可見性之間的關系之前發生。

class Main {
    JFrame initFrame = null;
    AppFrame appFrame = null;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
               initFrame = new InitFrame();
               initFrame.setVisible(true);
            }
        });

        while(!initFrame.isShowing()) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
            }
        }

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                appFrame = new AppFrame();
                appFrame.setVisible(true);
                initFrame.setVisible(false);
                initFrame.dispose();
            }
        });

    }
}

以下是AppFrame可能出現問題的示例。

您可以使用線程運行測試:

java SplashTest true

或沒有

java SplashTest

啟用線程后,您會看到SplashFrameAppFrameSplashFrame更新或更少。

如果未啟用線程,您將看到沒有顯示任何組件的SplashFrame ,應用程序“掛起”4秒,然后您會看到AppFrame

這個例子有點人為,但可能會給你一些想法。

請注意, SplashFrameAppFrame沒有“直接”連接。 所有通信都通過AppFrameWorkListener接口完成。

我還把'work'放在了AppFrame 但實際上,如果要進行大量處理,則應該從UI代碼中提取,在單獨的Thread運行,並且AppFrame將被通知任務的進度,與SplashFrame當前的方式相同。

import javax.swing.*;

class SplashTest {

    static boolean useThread = false;

    public static void main(String[] args) {
        // Pass true at the command line to turn on threading.
        // No args, or any value other than true will turn off threading.
        if (args.length > 0) {
            useThread = new Boolean(args[0]);
        }
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                SplashFrame splashFrame = new SplashFrame();
                splashFrame.setVisible(true);
                new AppFrame(splashFrame).setVisible(true);
            }});
    }

    private static class BaseFrame extends JFrame {
        public BaseFrame() {
            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            setSize(200, 200);
            setTitle(getClass().getSimpleName());
        }
    }

    private static class SplashFrame extends BaseFrame implements AppFrameWorkListener {
        JLabel status;

        public SplashFrame() {
            setLocation(0, 0);
            status = new JLabel("Splash Frame");
            getContentPane().add(status);
        }

        public void appFrameWorkStart() {
            status.setText("Work started");
        }

        public void appFrameWorkProgress(long timeElapsed) {
            status.setText("Work has taken " + timeElapsed + "ms so far");
        }

        public void appFrameWorkDone() {
            // http://stackoverflow.com/questions/1234912/how-to-programmatically-close-a-jframe
            setVisible(false);
            dispose();
        }
    }

    private static class AppFrame extends BaseFrame {
        JLabel status;
        AppFrameWorkListener listener;

        public AppFrame(AppFrameWorkListener listener) {
            setLocation(200, 200);
            status = new JLabel("App Frame");
            getContentPane().add(status);

            this.listener = listener;

            // None of this 'heavy lifting' should be in a constructor.
            if (useThread) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        doLotsOfWork(4);
                    }
                }).start();
            } else {
                doLotsOfWork(4);
                onWorkDone();
            }
        }

        private void doLotsOfWork(int workLengthSeconds) {
            // We're starting. Ensure onWorkStart is called on the EDT,
            // as this method may be called from a different Thread.
            invokeOnWorkStartOnEDT();

            long start = System.currentTimeMillis();

            // Hammer the CPU for "workLengthSeconds" number of seconds.
            // And do some contrived progress reporting.
            long workLengthMs = workLengthSeconds * 1000;
            while (System.currentTimeMillis() - start < workLengthMs) {
                long innerStart = System.currentTimeMillis();
                // Consume 250ms CPU before issuing progress update.
                while (System.currentTimeMillis() - innerStart < 250);
                invokeOnWorkProgressOnEDT(System.currentTimeMillis() - start);
            }

            // We're done now. Ensure onWorkDone is called on the EDT,
            // as this method may be called from a different Thread.
            invokeOnWorkDoneOnEDT();
        }

        private void invokeOnWorkStartOnEDT() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    onWorkStart();
                }
            });
        }

        private void invokeOnWorkProgressOnEDT(final long timeElapsed) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    onWorkProgress(timeElapsed);
                }
            });
        }

        private void invokeOnWorkDoneOnEDT() {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    onWorkDone();
                }
            });
        }

        private void onWorkStart() {
            status.setText("Work Started");
            if (null != listener) {
                // Tell someone who's interested in the work status.
                listener.appFrameWorkStart();
            }
        }

        private void onWorkProgress(long timeElapsed) {
            status.setText("Work has taken " + timeElapsed + "ms so far");
            if (null != listener) {
                // Tell someone who's interested in the work status.
                listener.appFrameWorkProgress(timeElapsed);
            }
        }

        private void onWorkDone() {
            status.setText("Work Done");
            if (null != listener) {
                // Tell someone who's interested in the work status.
                listener.appFrameWorkDone();
            }
        }
    }

    interface AppFrameWorkListener {
        public void appFrameWorkDone();
        public void appFrameWorkStart();
        public void appFrameWorkProgress(long timeElapsed);
    }

}

你應該使用Java Thread,你可以向你的用戶顯示一個交互式的Splash Screen(自定義),而你的代碼生成任何你想要的東西是一個教程只是看看

你應該使用Threads來實現良好和高效的並發性

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM