23.10. Multithreading with GUI
This program uses separate threads to modify the content displayed in a Swing GUI. The nature of multithreaded programming
Our
Figure 23.17.
RunnableObject
outputs a random uppercase letter on a
JLabel
. Allows
|
1 // Fig. 23.17: RunnableObject.java 2 // Runnable that writes a random character to a JLabel 3 import java.util.Random; 4 import java.util.concurrent.locks.Condition; 5 import java.util.concurrent.locks.Lock; 6 import javax.swing.JLabel; 7 import javax.swing.SwingUtilities; 8 import java.awt.Color; 9 10 public class RunnableObject implements Runnable 11 { 12 private static Random generator = new Random(); // for random letters 13 private Lock lockObject; // application lock; passed in to constructor 14 private Condition suspend; // used to suspend and resume thread 15 private boolean suspended = false ; // true if thread suspended 16 private JLabel output; // JLabel for output 17 18 public RunnableObject( Lock theLock, JLabel label ) 19 { 20 lockObject = theLock; // store the Lock for the application 21 suspend = lockObject.newCondition(); // create new Condition 22 output = label; // store JLabel for outputting character 23 } // end RunnableObject constructor 24 25 // place random characters in GUI 26 public void run() 27 { 28 // get name of executing thread 29 final String threadName = Thread.currentThread().getName(); 30 31 while ( true ) // infinite loop; will be terminated from outside 32 { 33 try 34 { 35 // sleep for up to 1 second 36 Thread.sleep( generator.nextInt( 1000 ) ); 37 38 lockObject.lock(); // obtain the lock 39 try 40 { 41 while ( suspended ) // loop until not suspended 42 { 43 suspend.await(); // suspend thread execution 44 } // end while 45 } // end try 46 finally 47 { 48 lockObject.unlock(); // unlock the lock 49 } // end finally 50 } // end try 51 // if thread interrupted during wait/sleep 52 catch ( InterruptedException exception ) 53 { 54 exception.printStackTrace(); // print stack trace 55 } // end catch 56 57 // display character on corresponding JLabel 58 SwingUtilities.invokeLater( 59 new Runnable() 60 { 61 // pick random character and display it 62 public void run() 63 { 64 // select random uppercase letter 65 char displayChar = 66 ( char ) ( generator.nextInt( 26 ) + 65 ); 67 68 // output character in JLabel 69 output.setText( threadName + ": " + displayChar ); 70 } // end method run 71 } // end inner class 72 ); // end call to SwingUtilities.invokeLater 73 } // end while 74 } // end method run 75 76 // change the suspended/running state 77 public void toggle() 78 { 79 suspended = !suspended; // toggle boolean controlling state 80 81 // change label color on suspend/resume 82 output.setBackground( suspended ? Color.RED : Color.GREEN ); 83 84 lockObject.lock(); // obtain lock 85 try 86 { 87 if ( !suspended ) // if thread resumed 88 { 89 suspend.signal(); // resume thread 90 } // end if 91 } // end try 92 finally 93 { 94 lockObject.unlock(); // release lock 95 } // end finally 96 } // end method toggle 97 } // end class RunnableObject |
Class
RunnableObject
(Fig. 23.17) implements interface
Runnable
's
run
method (lines 2674). Line 29 uses
static Thread
method
currentThread
to determine the currently executing thread and
Thread
method
getName
to return the name of that thread. Every executing thread has a default name that includes the number of the thread (see the output of Fig. 23.18). Lines 3173 are an infinite loop. [
Note:
In earlier chapters we have said that infinite loops are bad programming because the application will not terminate. In this case, the infinite loop is in a separate thread from the main thread. When the application window is closed in this example, all the threads created by the main thread are closed as well, including threads (such as this one) that are executing infinite
1 // Fig. 23.18: RandomCharacters.java 2 // Class RandomCharacters demonstrates the Runnable interface 3 import java.awt.Color; 4 import java.awt.GridLayout; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.util.concurrent.Executors; 8 import java.util.concurrent.ExecutorService; 9 import java.util.concurrent.locks.Condition; 10 import java.util.concurrent.locks.Lock; 11 import java.util.concurrent.locks.ReentrantLock; 12 import javax.swing.JCheckBox; 13 import javax.swing.JFrame; 14 import javax.swing.JLabel; 15 16 public class RandomCharacters extends JFrame implements ActionListener 17 { 18 private final static int SIZE = 3 ; // number of threads 19 private JCheckBox checkboxes[]; // array of JCheckBoxes 20 private Lock lockObject = new ReentrantLock( true ); // single lock 21 22 // array of RunnableObjects to display random characters 23 private RunnableObject[] randomCharacters = 24 new RunnableObject[ SIZE ]; 25 26 // set up GUI and arrays 27 public RandomCharacters() 28 { 29 checkboxes = new JCheckBox[ SIZE ]; // allocate space for array 30 setLayout( new GridLayout( SIZE , 2 , 5 , 5 ) ); // set layout 31 32 // create new thread pool with SIZE threads 33 ExecutorService runner = Executors.newFixedThreadPool( SIZE ); 34 35 // loop SIZE times 36 for ( int count = ; count < SIZE ; count++ ) 37 { 38 JLabel outputJLabel = new JLabel(); // create JLabel 39 outputJLabel.setBackground( Color.GREEN ); // set color 40 outputJLabel.setOpaque( true ); // set JLabel to be opaque 41 add( outputJLabel ); // add JLabel to JFrame 42 43 // create JCheckBox to control suspend/resume state 44 checkboxes[ count ] = new JCheckBox( "Suspended" ); 45 46 // add listener which executes when JCheckBox is clicked 47 checkboxes[ count ].addActionListener( this ); 48 add( checkboxes[ count ] ); // add JCheckBox to JFrame 49 50 // create a new RunnableObject 51 randomCharacters[ count ] = 52 new RunnableObject( lockObject, outputJLabel ); 53 54 // execute RunnableObject 55 runner.execute( randomCharacters[ count ] ); 56 } // end for 57 58 setSize( 275 , 90 ); // set size of window 59 setVisible( true ); // show window 60 61 runner.shutdown(); // shutdown runner when threads finish 62 } // end RandomCharacters constructor 63 64 // handle JCheckBox events 65 public void actionPerformed( ActionEvent event ) 66 { 67 // loop over all JCheckBoxes in array 68 for ( int count = ; count < checkboxes.length; count++ ) 69 { 70 // check if this JCheckBox was source of event 71 if ( event.getSource() == checkboxes[ count ] ) 72 randomCharacters[ count ].toggle(); // toggle state 73 } // end for 74 } // end method actionPerformed 75 76 public static void main( String args[] ) 77 { 78 // create new RandomCharacters object 79 RandomCharacters application = new RandomCharacters(); 80 81 // set application to end when window is closed 82 application.setDefaultCloseOperation( EXIT_ON_CLOSE ); 83 } // end main 84 } // end class RandomCharacters
|
When the thread awakens, line 38 acquires the Lock on this application. Lines 4144 loop while the boolean variable suspended is TRue . Line 43 calls method await on Condition suspend to temporarily release the Lock and place this thread into the waiting state. When this thread is signaled , it reacquires the Lock , moves back to the runnable state and releases the Lock (line 48). When suspended is false , the thread should resume execution. If suspended is still true , the loop executes again.
Lines 5872 call SwingUtilities method invokeLater . Lines 5971 declare an anonymous inner class that implements the Runnable interface, and lines 6270 declare method run . The method call to invokeLater places this Runnable object in a queue to be executed by the event-dispatching thread. Lines 6566 create a random uppercase character. Line 69 calls method setText on the JLabel output to display the thread name and the random character on the JLabel in the application window.
When the user clicks the
JCheckBox
to the right of a particular
JLabel
, the corresponding thread should be suspended (temporarily prevented from executing) or resumed (allowed to continue executing). Suspending and resuming of a thread can be implemented by using thread synchronization and
Condition
Note that the if statement in line 87 does not have an associated else . If this condition fails, it means that the thread has just been suspended. When this happens, a thread executing at line 38 will enter the while loop and line 43 will suspend the thread with a call to method await .
Class
RandomCharacters
(Fig. 23.18) displays three
JLabels
and three
JCheckBoxes
. A separate thread of execution is associated with each
JLabel
and
JCheckBox
pair. Each thread
If the user clicks the Suspended checkbox next to a particular JLabel , the program invokes method actionPerformed (lines 6574) to determine which checkbox generated the event. Lines 6873 determine which checkbox generated the event. Line 71 checks whether the source of the event is the JCheckBox in the current index. If it is, line 72 calls method toggle (lines 7592 of Fig. 23.17) on that RunnableObject .