在stackoverflow上的人的帮助下,我能够得到以下简单GUI倒计时的工作代码(它只显示一个倒数秒的窗口)。我对此代码的主要问题是invokeLater
内容。
据我了解invokeLater
,它向事件调度线程(EDT)发送任务,然后EDT在“可以”(无论这意味着什么)时执行此任务。 是吗?
根据我的理解,代码的工作原理如下:
在main
方法中,我们使用invokeLater
来显示窗口(showGUI
方法)。换句话说,显示窗口的代码将在EDT中执行。
在main
方法中,我们也启动counter
,并且计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。右
counter
在一个单独的线程中执行,并定期调用updateGUI
。 updateGUI
应该更新GUI。 GUI正在EDT中运行。因此,updateGUI
也应该在EDT中执行。这就是updateGUI
的代码包含在invokeLater
中的原因。是吗?
我不清楚为什么我们从美国东部时间拨打counter
。无论如何,它不是在EDT中执行的。它立即启动,一个新线程并在那里执行counter
。那么,为什么我们不能在counter
阻止后调用main方法中的invokeLater
?
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();
}
});
}
}
答案 0 :(得分:16)
如果我理解你的问题,你会想知道为什么你不能这样做:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
}
});
counter.start();
}
你不能这样做的原因是因为调度程序不能保证...只是因为你调用了showGUI()
然后调用counter.start()
并不意味着showGUI()
中的代码1}}将在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();
}
});
}
现在showGUI();
和counter.start();
从同一个线程执行,因此JLabel
将在counter
启动之前创建。
更新:
问: 我不明白这个帖子有什么特别之处。
A: Swing事件处理代码在称为事件派发线程的特殊线程上运行。大多数调用Swing方法的代码也在这个线程上运行。这是必要的,因为大多数Swing对象方法都不是“线程安全的”:从多个线程调用它们会冒线程干扰或内存一致性错误。 1问: 那么,如果我们有一个GUI,为什么要在一个单独的线程中启动它?
答:可能有一个比我更好的答案,但如果你想从EDT更新GUI(你这样做),那么你必须从EDT开始。问: 为什么我们不能像其他线程一样启动线程?
A:请参阅上一个答案。问: 为什么我们使用一些invokeLater以及为什么这个线程(EDT)在准备就绪时开始执行请求。为什么它不总是准备好?
A: EDT可能还有其他一些需要处理的AWT事件。invokeLater
使得doRun.run()在AWT事件派发线程上异步执行。这将在处理完所有挂起的AWT事件后发生。当应用程序线程需要更新GUI时,应使用此方法。 2
答案 1 :(得分:2)
您实际上从EDT开始 counter
主题。如果您在counter.start()
阻止后调用invokeLater
,则计数器可能会在 GUI变为可见之前开始运行。现在,因为您正在EDT中构建GUI,所以当counter
开始更新时,GUI将不会存在。幸运的是,您似乎将GUI更新转发到EDT,这是正确的,并且由于EventQueue是一个队列,因此第一次更新将在构建GUI之后发生,因此应该没有理由不这样做。但更新可能不可见的GUI有什么意义呢?
答案 2 :(得分:1)
什么是EDT?
围绕Swing API出现的大量并发问题,这是一个hacky解决方法;)
说真的,很多Swing组件都不是“线程安全的”(一些着名的程序员甚至称为Swing“线程敌对”)。通过拥有一个独特的线程,对这个线程恶意的组件进行所有更新,你就会避免很多潜在的并发问题。除此之外,您还可以保证它将按顺序使用Runnable
运行您通过它的invokeLater
。
然后一些挑剔:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
然后:
在主要方法中我们也开始了 柜台和柜台(由 建筑)在另一个执行 线程(所以它不在事件中 调度线程)。正确?
你并没有真正在主方法中启动计数器。您在EDT上执行的匿名Runnable
的 run()方法中启动计数器。所以你真的从EDT开始计数器Thread
,而不是主要方法。然后,因为它是一个单独的线程,它在EDT上不是运行。但是计数器肯定是 在EDT上启动,而不是Thread
执行main(...)
方法。
这是挑剔,但我认为这个问题仍然很重要。
答案 3 :(得分:0)
这很简单,如下
第1步。初始线程也称为主线程。
步骤2.创建一个可运行的对象并将其传递给invokeLate()。
步骤3.这会初始化GUI但不会创建GUI。
步骤4. InvokeLater()调度创建的对象以便在EDT上执行。
步骤5.已创建GUI。
步骤6.所有发生的事件都将放在EDT中。