With the help of people on stackoverflow I was able to get the following working code of a simple GUI countdown (which just displays a window counting down seconds). My main problem with this code is the invokeLater stuff.
As far as I understand invokeLater, it sends a task to the event dispatching thread (EDT) and then the EDT executes this task whenever it “can” (whatever that means). Is that right?
To my understanding, the code works like this:
-
In the
mainmethod we useinvokeLaterto show the window (showGUImethod). In other words, the code displaying the window will be executed in the EDT. -
In the
mainmethod we also start thecounterand the counter (by construction) is executed in another thread (so it is not in the event dispatching thread). Right? -
The
counteris executed in a separate thread and periodically it callsupdateGUI.updateGUIis supposed to update the GUI. And the GUI is working in the EDT. So,updateGUIshould also be executed in the EDT. It is the reason why the code for theupdateGUIis enclosed ininvokeLater. Is that right?
What is not clear to me is why we call the counter from the EDT. Anyway, it is not executed in the EDT. It starts immediately, a new thread and the counter is executed there. So, why can we not call the counter in the main method after the invokeLater block?
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();
}
});
}
}
If I understand your question correctly you’re wonder why you can’t do this:
The reason why you can’t do it is because the scheduler makes no guarantees… just because you invoked
showGUI()and then you invokedcounter.start()doesn’t mean that the code inshowGUI()will be executed before the code in the run method of thecounter.Think of it this way:
starts a thread and that thread isschedules an asynchronous event on the EDT which is tasked with creating theJLabel.JLabelto exists so it can calllabel.setText("You have " + i + " seconds.");Now you have a race condition:
JLabelmust be created BEFORE thecounterthread starts, if it’s not created before the counter thread starts, then your counter thread will be callingsetTexton an uninitialized object.In order to ensure that the race condition is eliminated we must guarantee the order of execution and one way to guarantee it is to execute
showGUI()andcounter.start()sequentially on the same thread:Now
showGUI();andcounter.start();are executed from the same thread, thus theJLabelwill be created before thecounteris started.Update: