Rom*_*man 12 java events user-interface multithreading invoke
在stackoverflow上的人的帮助下,我能够获得以下简单GUI倒计时的工作代码(它只显示一个倒数秒的窗口).我对这段代码的主要问题是这些invokeLater
东西.
据我所知invokeLater
,它向事件调度线程(EDT)发送任务,然后EDT在"可以"(无论这意味着什么)时执行该任务.是对的吗?
根据我的理解,代码的工作原理如下:
在main
我们用于invokeLater
显示窗口(showGUI
方法)的方法中.换句话说,显示窗口的代码将在EDT中执行.
在该main
方法中,我们也启动counter
并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中).对?
它counter
在一个单独的线程中执行,并定期调用updateGUI
.updateGUI
应该更新GUI.GUI正在EDT中运行.所以,updateGUI
也应该在EDT中执行.这就是为什么该代码的原因updateGUI
被封闭在invokeLater
.是对的吗?
我不清楚的是为什么我们打电话counter
给EDT.无论如何,它不是在EDT中执行的.它立即启动,一个新线程并在counter
那里执行.那么,为什么我们不能counter
在invokeLater
块之后调用main方法呢?
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
public class CountdownNew {
static JLabel label;
// Method which defines the appearance of the window.
public static void showGUI() {
JFrame frame = new JFrame("Simple Countdown");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
label = new JLabel("Some Text");
frame.add(label);
frame.pack();
frame.setVisible(true);
}
// Define a new thread in which the countdown is counting down.
public static Thread counter = new Thread() {
public void run() {
for (int i=10; i>0; i=i-1) {
updateGUI(i,label);
try {Thread.sleep(1000);} catch(InterruptedException e) {};
}
}
};
// A method which updates GUI (sets a new value of JLabel).
private static void updateGUI(final int i, final JLabel label) {
SwingUtilities.invokeLater(
new Runnable() {
public void run() {
label.setText("You have " + i + " seconds.");
}
}
);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
}
Run Code Online (Sandbox Code Playgroud)
Kir*_*ril 16
如果我正确理解你的问题,你会想知道为什么你不能这样做:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
}
});
counter.start();
}
Run Code Online (Sandbox Code Playgroud)
你不能这样做的原因是因为调度程序不保证...只是因为你调用showGUI()
然后你调用counter.start()
并不意味着代码showGUI()
将在run方法中的代码之前执行counter
.
想一想:
JLabel
.JLabel
存在,因此它可以调用label.setText("You have " + i + " seconds.");
现在你有一个竞争条件: JLabel
必须在counter
线程启动之前创建,如果它不是在计数器线程启动之前创建的,那么你的计数器线程将调用setText
未初始化的对象.
为了确保消除竞争条件,我们必须保证执行的顺序,并保证一种方法是在同一个线程上执行showGUI()
和counter.start()
顺序执行:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
Run Code Online (Sandbox Code Playgroud)
现在showGUI();
,counter.start();
从同一个线程执行,因此JLabel
将在counter
启动之前创建.
更新:
问: 我不明白这个帖子有什么特别之处.
答: Swing事件处理代码在称为事件派发线程的特殊线程上运行.大多数调用Swing方法的代码也在这个线程上运行.这是必要的,因为大多数Swing对象方法都不是"线程安全的":从多个线程调用它们会冒线程干扰或内存一致性错误.1问: 那么,如果我们有一个GUI,我们为什么要在一个单独的线程中启动它?
答:可能有一个比我更好的答案,但是如果你想从EDT(你做的)更新GUI,那么你必须从EDT开始.问: 为什么我们不能像其他任何线程一样启动线程?
答:见上一个答案.问: 为什么我们使用一些invokeLater以及为什么这个线程(EDT)在准备就绪时开始执行请求.为什么它不总是准备好?
答: EDT可能还有一些其他必须处理的AWT事件.invokeLater
导致doRun.run()在AWT事件派发线程上异步执行.这将在处理完所有挂起的AWT事件后发生.当应用程序线程需要更新GUI时,应使用此方法.2