Flylib.com

Books Software

 
 
 

Section 23.10. Multithreading with GUI


[Page 1087 ( continued )]

23.10. Multithreading with GUI

This program uses separate threads to modify the content displayed in a Swing GUI. The nature of multithreaded programming prevents the programmer from knowing exactly when a thread will execute. Swing components are not thread safe if multiple threads manipulate a Swing GUI component, the results may not be correct. All interactions with Swing GUI components should be performed as part of the event-dispatching thread (also known as the event-handling thread ). Class SwingUtilities (package javax.swing ) provides static method invokeLater to help with this process. Method invokeLater specifies GUI processing statements to execute later as part of the event-dispatching thread. Method invokeLater receives as its argument an object that implements interface Runnable . The method places the Runnable as an event into the event-dispatching thread's queue of events. These events are processed in the order they appear in the queue. Because only one thread handles these events, it can be guaranteed that the GUI will be updated properly.


[Page 1088]

Our next example (Fig. 23.17) demonstrates this concept. When this program calls invokeLater , the GUI component update will be queued for execution in the event-dispatching thread. The Runnable 's run method will then be invoked as part of the event-dispatching thread to perform the output and ensure that the GUI component is updated in a thread-safe manner. This example also demonstrates how to suspend a thread (i.e., temporarily prevent it from executing) and how to resume a suspended thread.

Figure 23.17. RunnableObject outputs a random uppercase letter on a JLabel . Allows user to suspend and resume execution.
(This item is displayed on pages 1088 - 1090 in the print version)
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 loops .] In each iteration of the loop, the thread sleep s for a random interval from 0 to 1 seconds (line 36).

Figure 23.18. RandomCharacters creates a JFrame with three RunnableObjects and three JCheckBoxes to allow the user to suspend and resume threads.
(This item is displayed on pages 1091 - 1093 in the print version)
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



{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

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.


[Page 1090]

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 methods await and signal . Lines 7796 declare method toggle , which will change the suspended/resumed state of the current thread. Line 79 reverses the value of boolean variable suspended . Line 82 changes the background color of the JLabel by calling method setBackground . If the thread is suspended, the background color will be Color.RED , whereas if the thread is running, the background color will be Color.GREEN . Method toggle is called from the event handler in Fig. 23.18, so its tasks will be performed in the event-dispatch threadthus, there is no need to use invokeLater for line 82. Line 84 acquires the Lock for this application. Line 87 then tests whether the thread was just resumed. If this is true, line 89 calls method signal on Condition suspend . This method call will alert a thread that was placed in the waiting state by the await method call in line 43. Line 94 releases the Lock on this application inside a finally block.

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 .


[Page 1091]

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 randomly displays letters from the alphabet in its corresponding JLabel object. Line 33 creates a new ExecutorService with method newFixedThreadPool . Lines 3656 iterate three times. Lines 3841 create and customize the JLabel . Line 44 creates the JCheckBox , and line 47 adds an ActionListener for each JCheckBox (this will be discussed later.) Lines 5152 create a new RunnableObject implements the Runnable interface. Line 55 executes the RunnableObject with one of the threads of execution created in runner in line 33.

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 .


[Page 1093]