Applications that have graphical user interfaces (GUIs) are naturally multithreaded. Usually you have one thread that draws the GUI, and another thread that runs the logic behind the scenes. By splitting these two tasks into separate threads, you can get a nice performance boost if each thread runs on a separate core. The GUI is quick n' snappy. The user is happy. And the developer is happy, well, for the most part if the job gets done correctly (issues of race conditions, deadlocks, and starvation may be covered in a future post).
Moving on, Java SE6 provides two convenient libraries for getting our hands dirty with GUI-based multithreading, Swing and Thread. I used these libraries to whip up an application to spawn multiple threads, assign a priority to each thread, perform a computationally intensive calculation in a loop, and report the average time for each thread to complete a calculation.
|Screenshot of Swing application|
- Create the GUI elements like the frame, start button, dropdown lists, panels, checkboxes, and labels.
- Attach an event listener to the start button.
- Set the frame to visible with frame.setVisible(true).
- Handle an event in the actionPerformed method when the user clicks the start button. The event handler checks the number of threads that the user wants to start. Then it creates the threads with the user-selected thread priority.
- Calculate the sum of the first 1000 cosines for 120 times in a loop for 3 minutes on each thread.
- After each iteration of the loop, calculate the average amount of time it took (in milliseconds) to perform the calculation and update the GUI.
So how does this relate to GUI-based multithreading? Swing incorporates a separate thread called the Event Dispatch Thread (EDT). It is the thread that is responsible for handling all events and updating the GUI. This means that you, as a programmer, cannot manually update the GUI yourself in the main thread or a runnable thread that you have created yourself. To update a GUI component properly, you must either place your code in the actionPerformed method (because it runs on the EDT), or place your code in another class and invoke it using the SwingUtilities.invokeLater method (which tells it to run on the EDT).
The main idea is to break the program into threads that perform background computation and thread(s) that update the GUI (the EDT does this for us). Since you want to keep the GUI responsive, and the EDT is responsible for updating the GUI, you don't want to have long-running pieces of code on the EDT. Instead, have the EDT spawn off new threads and run your (slower) code in these new threads. Proper usage of these concepts can be seen in my program and source code here. Compile with "javac ManyPanels.java" and run with "java ManyPanels 0".
I ran an experiment to see how thread priority scheduling works on different operating systems. On a Windows machine with 2 cores and a Mac machine with 2 cores, I ran 10 threads with priorities ranging from 1 to 10. The first number under each panel indicates the average amount of time (in milliseconds) it took to get through one iteration, and the second number in parentheses indicates the number of iterations that the thread was able to execute in 3 minutes.
Windows. Thread priorities are all over the place. A thread with priority 1 should not run faster than a thread with priority 8.
|Mac. Thread priorities are in order, with 1 running the slowest and 10 running the fastest.|
Great article, although http://www2.hawaii.edu/~lindavid/ics432/ManyPanels.java can't be accessed (403 error, forbidden)ReplyDelete