[英]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
啟用線程后,您會看到SplashFrame
和AppFrame
每SplashFrame
更新或更少。
如果未啟用線程,您將看到沒有顯示任何組件的SplashFrame
,應用程序“掛起”4秒,然后您會看到AppFrame
。
這個例子有點人為,但可能會給你一些想法。
請注意, SplashFrame
與AppFrame
沒有“直接”連接。 所有通信都通過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.