For some people, hearing the word "thread" brings to mind spiders, or else other creeping things which can be seen on dark nights when one is coding alone in the office. However, this should not be the case, for in a Java program threads are your friend, and perhaps unbeknownst to you, they have been aiding your adventures from the first time you used the Swing library.
Before we slide down into a possible tangle of multiple threads, remember that one should not begin creating threads without a good purpose, for they are complex and need to be completely thought through before being used. When you are creating threaded code, keep it as simple as possible, for any complexity you introduce will surely lead you into some sticky situations like a moth who gets trapped forever in a deadlocked situation.
You may be surprised to find out that Swing already uses multiple threads. "How is this possible?" you might ask. "I have never implemented a Runnable interface nor extended Thread in my years of using Swing." Swing utilizes something called the event dispatch thread which operates behind the scenes. This thread is responsible for handling system events, such as when a user clicks the mouse button or when a Swing timer goes off. Fortunately, event handling code automatically executes in the event dispatch thread, so all of your callbacks are already taking place on this thread. When the user clicks on one of your controls, the event is handled by the event dispatch thread and your code that responds to this event is executed on this separate thread.
Try running this example which shows the name of the thread in the label on the left. Source Code
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ThreadDemo extends JFrame {
JLabel label;
public ThreadDemo() {
super("Thread Demo");
setSize(300,50);
this.getContentPane().setLayout(new GridLayout(1,2));
label = new JLabel();
label.setText(Thread.currentThread().getName());
this.getContentPane().add(label);
JButton button = new JButton();
button.setText("Get Thread");
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
label.setText(Thread.currentThread().getName());
}
};
button.addActionListener(listener);
this.getContentPane().add(button);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
}
public static void main(String args[]) {
new ThreadDemo();
}
}
The first time the call Thread.currentThread().getName() is invoked, it is in the thread named "main", because the label is created within the main thread. However, within the ActionListener which is invoked when you press the on screen button the thread name is "AWT-EventQueue-0". The actionPerformed call is invoked when you click the mouse button, which is handled in the event dispatch thread. You can use this technique of checking the thread name to ensure any code you are writing is actually being run in the event dispatch thread.
One of the fortunate things about the fact that events are handled automatically on the event dispatch thread is that Swing is not thread safe and you must modify any realized GUI components from within the event dispatch thread. Thus, the difficult and dangerous task of keeping Swing thread-safe is happening by default for your event handling code, and is already taking place within this context. You could imagine if a separate thread began modifying a combo box at the same moment a user chose that combo box and started scrolling through it. The technical term for such a confluence of events is "uh oh". In the worst case, not only will data integrity be compromised, but the entire application will lock up, and the hours of work the user has spent using your application will vanish into a cloud of smoke coming out of his or her ears.
Historically there has been one mighty exception to the rule that you must modify any GUI components from within the event dispatch thread; that was at startup. It had been considered safe to create the GUI in the application's main thread provided no GUI components were visible. This is the way most programs are written, and is likely to be safe, but now as you can see the official way to ensure complete safety is to now also create the GUI itself within the event dispatch thread. This will ensure you have no lockup at startup.
There are two methods for invoking code inside the event dispatch thread when you are not already in that thread: invokeLater and invokeAndWait. The invokeLater is utilized by passing in a Runnable interface object with a run method which executes at a later time on the event dispatch thread. The invokeAndWait operates in the same way, but does not return until the event dispatch thread has finished executing the code. When you create these Runnable objects and pass them to the invoke methods, they are executed on the event dispatch thread.
Below is the code to replace the main method coded above with the creation of the GUI taking place on the event dispatch thread. An anonymous class is created with the Runnable interface which calls the new ThreadDemo(); to create the GUI. As you can tell if you run the modified code, the creation of the label now takes place on the event dispatch thread. Source Code
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new ThreadDemo();
}
});
}
Using other threads
If you have ever used an application and wondered "What the heck is taking so long here?" you have encountered a reason to use multiple threads. The user is in charge of your application, and as soon as you wander off with the event dispatch thread with an extremely slow piece of code, your user has lost control. The application will appear to hang, since no other mouse or keyboard events can be handled as long as the event dispatch thread is busy.
For this sample application we will compute the value of P using perhaps the slowest algorithm possible. Since P is an irrational number, a complete implementation could take forever to complete. In case the user is not willing to wait forever, we will utilize multiple threads: one thread to compute the value of P, and the other default event dispatch thread to keep the user informed of what we think P is at the moment. For this example we store the approximated value of pi in a double.
As an aside, the way we are computing P here is by throwing random darts that hit a square with a quarter of a circle inscribed in it. The ratio of darts which fall within the circle to the total number of darts thrown gives a way to approximate P. The square is 1 unit across, and the circle has a radius of 1 unit. The complete circle has an area of P * radius * radius so the quarter circle has an area of P / 4. Thus P is approximately equal to 4 * the number of circle quadrant hits divided by the number of throws.
P is roughly equal to the 4 * number of green dots / (number of green dots + red dots).
Since this is random, there is no guarantee we will converge on P- all of the darts may well fall into the circle quadrant and it will appear P is very close to 4.0. In reality the most significant digits of PI will be calculated fairly quickly and it will take a very long time to find additional significant digits. This is useful, however, as an example utilizing concurrent threading with Swing. Source Code
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class CalculatePi extends JFrame {
JLabel label;
volatile double pi = 0;
synchronized void setPi(double value) {
pi = value;
}
synchronized double getPi() {
return pi;
}
class ThrowDarts implements Runnable {
public void run() {
long counter = 0;
long hits = 0;
double x = 0;
double y = 0;
while (counter < Long.MAX_VALUE)
{
counter++;
x = Math.random();
y = Math.random();
if (Math.sqrt(x*x + y*y) < 1.0f)
{
hits++;
}
setPi(4 * (double) hits / (double) counter);
if (counter%1000 == 0)
{
try {
javax.swing.SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
label.setText("" + getPi());
}
});
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
public CalculatePi() {
super("Throwing Darts");
setSize(300,50);
label = new JLabel();
label.setText(Thread.currentThread().getName());
this.getContentPane().add(label);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
ThrowDarts dartThrower = new ThrowDarts();
Thread t = new Thread(dartThrower);
t.start();
}
public static void main(String args[]) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
new CalculatePi();
}
});
}
}
Notice that the variable pi is declared to be volatile. This tells the compiler not to place the value of pi into any registers so that it can be accessed from any thread. To guarantee that any accesses to the value of pi are atomic, that is taking place in a single operation, both set and get methods exist with the keyword synchronized to make sure that these operations complete fully before any other thread executes. The code that runs within these synchronized methods will be mutually exclusive; both of these methods can not be executed at once by different threads. Depending on the Java Virtual Machine implementation potentially half of the bytes of pi could be accessed when the other thread changes the value.
At regular intervals, whenever the counter is a multiple of 1000, an anonymous class implementing Runnable is created that updates the label. It is passed to invokeAndWait which will wait for the event dispatch thread to return before processing further. In this way, the update to the label that shows pi takes place on the event dispatch thread.
The invokeAndWait will wait for the event dispatch thread to return before computing more, thus preventing us from adding too many Runnable objects on the event dispatch thread. Do not create too many Runnable objects on the event dispatch thread or the thread can get bogged down. An alternative technique would be to utilize a timer to periodically update the label.
In the main method of the program, the interface is created on the event dispatch thread with its own anonymous instantiation of the Runnable interface.
Parting ideas
There are some methods which are thread safe within the Swing component hierarchy. They will be marked in the documentation as "This method is thread safe".
In summary, there is no need to create separate threads for the general Swing application, although you are advised to instantiate your GUI on the event dispatch thread. All of your event handling code will take place on the event dispatch thread by default. If you are doing something advanced that does require multiple threads, be sure to make it thread safe and manipulate Swing components from within the event dispatch thread. Java Virtual Machine implementations of threading are not consistent so code that works in your test environment may fail elsewhere unless you are careful. Unless the documentation explicitly states that methods are thread safe, you should assume that they are not.
Friday, March 21, 2008
Synchronized Multithreading with Swing
Posted by Prem Kumar Jha at 7:03:00 PM 2 comments
Labels: Swing
Subscribe to:
Posts (Atom)