Java 事件分派线程是如何工作的?
在人们的帮助下,我能够得到以下简单GUI倒计时的工作代码(只显示一个倒计时秒的窗口)。这段代码的主要问题是Java 事件分派线程是如何工作的?,java,user-interface,events,multithreading,invoke,Java,User Interface,Events,Multithreading,Invoke,在人们的帮助下,我能够得到以下简单GUI倒计时的工作代码(只显示一个倒计时秒的窗口)。这段代码的主要问题是invokeLater之类的东西 据我所知,invokeLater,它向事件调度线程(EDT)发送一个任务,然后EDT“可以”时执行该任务(不管这意味着什么)是这样吗? import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; public class Countdown
invokeLater
之类的东西
据我所知,invokeLater
,它向事件调度线程(EDT)发送一个任务,然后EDT“可以”时执行该任务(不管这意味着什么)是这样吗?
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();
}
});
}
}
据我所知,代码的工作原理如下:
main
方法中,我们使用invokeLater
来显示窗口(showGUI
方法)。换句话说,显示窗口的代码将在EDT中执行main
方法中,我们还启动计数器
,计数器(通过构造)在另一个线程中执行(因此它不在事件调度线程中)。对吧?计数器在单独的线程中执行,并定期调用updateGUI
<代码>更新GUI
应该更新GUI。图形用户界面在EDT中工作。因此,updateGUI
也应该在EDT中执行。这就是为什么updateGUI
的代码包含在invokeLater
中的原因。是这样吗计数器。无论如何,它不会在EDT中执行。它立即启动,一个新线程和计数器在那里执行。那么,为什么我们不能在调用器
块之后调用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();
}
});
}
}
实际上,您正在从EDT启动计数器
线程。如果在invokeLater
块之后调用counter.start()
,则计数器可能会在GUI可见之前开始运行。现在,因为您正在EDT中构建GUI,所以当计数器开始更新它时,GUI将不存在。幸运的是,您似乎正在将GUI更新转发给EDT,这是正确的,而且由于EventQueue是一个队列,第一次更新将在构建GUI之后发生,因此没有理由认为这不起作用。但是,更新可能还不可见的GUI有什么意义呢?如果我正确理解了您的问题,您会想知道为什么您不能这样做:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
}
});
counter.start();
}
您不能这样做的原因是因为调度程序不保证。。。仅仅因为您调用了showGUI()
,然后又调用了counter.start()
,并不意味着showGUI()
中的代码将在计数器的run方法中的代码之前执行
这样想:
- invokeLater启动一个线程,该线程在EDT上调度一个异步事件,该事件的任务是创建
JLabel
- 计数器是一个独立的线程,它依赖于
JLabel
的存在,因此它可以调用label.setText(“您有”+i+“秒”)代码>
现在您有了竞争条件:JLabel
必须在计数器
线程启动之前创建,如果它不是在计数器线程启动之前创建的,那么您的计数器线程将在未初始化的对象上调用setText
为了确保消除争用条件,我们必须保证执行顺序和单向
,以保证在同一线程上顺序执行showGUI()
和counter.start()
:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
现在showGUI()
和计数器.start()代码>从同一线程执行,因此JLabel
将在计数器启动之前创建
更新:
Q:我不明白这个帖子有什么特别之处。
A:Swing事件处理代码在一个称为事件调度线程的特殊线程上运行。大多数调用Swing方法的代码也在此线程上运行。这是必要的,因为大多数Swing对象方法都不是“线程安全的”:从多个线程调用它们可能会导致线程干扰或内存一致性错误
Q:那么,如果我们有一个GUI,为什么要在单独的线程中启动它呢?
A:可能有一个比我的答案更好的答案,但是如果您想从EDT更新GUI(确实如此),那么您必须从EDT启动它
Q:为什么我们不能像其他线程一样启动线程?
A:参见前面的答案
Q:为什么我们使用一些调用器,为什么这个线程(EDT)在请求准备就绪时开始执行请求。为什么它不总是准备好的?
A:EDT可能需要处理其他一些AWT事件。
invokeLater
导致在AWT事件调度线程上异步执行doRun.run()。这将在处理完所有挂起的AWT事件后发生。当应用程序线程需要更新GUI时,应使用此方法
什么是EDT
这是一种针对Swing API存在的大量并发问题的黑客解决方法;)
说真的,很多Swing组件都不是“线程安全的”(一些著名的程序员甚至称Swing为“线程不安全的”)。通过拥有一个唯一的线程,所有的更新都是针对该线程的组件进行的,您就避免了许多潜在的并发问题。除此之外,还保证它将运行Runnable
,您可以使用invokeLater
按顺序通过它
然后是吹毛求疵:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
showGUI();
counter.start();
}
});
}
然后:
在main方法中,我们还启动
柜台和柜台
构造)在另一个
线程(因此它不在事件中