Summary

Chapter 9 - Threads and Swing

Java Thread Programming
Paul Hyde
  Copyright 1999 Sams Publishing

Using a Worker Thread to Relieve the Event Thread
In BalanceLookupCantCancel , it became apparent that tying up the event thread to do an extensive operation was a bad idea. This was a problem especially because there was no way to signal that the search should be canceled . Another thread is needed to do the lookup so that the event thread can get back to the business of handling events.
BalanceLookup (see Listing 9.7) uses a worker thread to do the lengthy lookup and frees the event thread from this delay. This technique makes it possible to use the Cancel Search button to stop a search.
Listing 9.7  BalanceLookup.javaUsing a Worker Thread to Relieve the Event Thread
  1: import java.awt.*;
  2: import java.awt.event.*;
  3: import javax.swing.*;
  4:
  5: public class BalanceLookup extends JPanel {
  6:     private JTextField acctTF;
  7:     private JTextField pinTF;
  8:     private JButton searchB;
  9:     private JButton cancelB;
10:     private JLabel balanceL;
11:
12:     private volatile Thread lookupThread;
13:
14:     public BalanceLookup() {
15:         buildGUI();
16:         hookupEvents();
17:     }
18:
19:     private void buildGUI() {
20:         JLabel acctL = new JLabel(Account Number:);
21:         JLabel pinL = new JLabel(PIN:);
22:         acctTF = new JTextField(12);
23:         pinTF = new JTextField(4);
24:
25:         JPanel dataEntryP = new JPanel();
26:         dataEntryP.setLayout(new FlowLayout(FlowLayout.CENTER));
27:         dataEntryP.add(acctL);
28:         dataEntryP.add(acctTF);
29:         dataEntryP.add(pinL);
30:         dataEntryP.add(pinTF);
31:
32:         searchB = new JButton(Search);
33:         cancelB = new JButton(Cancel Search);
34:         cancelB.setEnabled(false);
35:
36:         JPanel innerButtonP = new JPanel();
37:         innerButtonP.setLayout(new GridLayout(1, -1, 5, 5));
38:         innerButtonP.add(searchB);
39:         innerButtonP.add(cancelB);
40:
41:         JPanel buttonP = new JPanel();
42:         buttonP.setLayout(new FlowLayout(FlowLayout.CENTER));
43:         buttonP.add(innerButtonP);
44:
45:         JLabel balancePrefixL = new JLabel(Account Balance:);
46:         balanceL = new JLabel(BALANCE UNKNOWN);
47:
48:         JPanel balanceP = new JPanel();
49:         balanceP.setLayout(new FlowLayout(FlowLayout.CENTER));
50:         balanceP.add(balancePrefixL);
51:         balanceP.add(balanceL);
52:
53:         JPanel northP = new JPanel();
54:         northP.setLayout(new GridLayout(-1, 1, 5, 5));
55:         northP.add(dataEntryP);
56:         northP.add(buttonP);
57:         northP.add(balanceP);
58:
59:         setLayout(new BorderLayout());
60:         add(northP, BorderLayout.NORTH);
61:     }
62:
63:     private void hookupEvents() {
64:         searchB.addActionListener(new ActionListener() {
65:                 public void actionPerformed(ActionEvent e) {
66:                     search();
67:                 }
68:             });
69:
70:         cancelB.addActionListener(new ActionListener() {
71:                 public void actionPerformed(ActionEvent e) {
72:                     cancelSearch();
73:                 }
74:             });
75:     }
76:
77:     private void search() {
78:         // better be called by event thread!
79:         ensureEventThread();
80:
81:         searchB.setEnabled(false);
82:         cancelB.setEnabled(true);
83:         balanceL.setText(SEARCHING ...);
84:
85:         // get a snapshot of this info in case it changes
86:         String acct = acctTF.getText();
87:         String pin = pinTF.getText();
88:
89:         lookupAsync(acct, pin);
90:     }
91:
92:     private void lookupAsync(String acct, String pin) {
93:         // Called by event thread, but can be safely
94:         // called by any thread.
95:         final String acctNum = acct;
96:         final String pinNum = pin;
97:
98:         Runnable lookupRun = new Runnable() {
99:                 public void run() {
100:                     String bal = lookupBalance(acctNum, pinNum);
101:                     setBalanceSafely(bal);
102:                 }
103:             };
104:        
105:         lookupThread = new Thread(lookupRun, lookupThread);
106:         lookupThread.start();
107:     }
108:    
109:     private String lookupBalance(String acct, String pin) {
110:         // Called by lookupThread, but can be safely
111:         // called by any thread.
112:         try {
113:             // Simulate a lengthy search that takes 5 seconds
114:             // to communicate over the network.
115:             Thread.sleep(5000);
116:
117:             // result retrieved, return it
118:             return 1,234.56;
119:         } catch (InterruptedException x) {
120:             return SEARCH CANCELLED;
121:         }
122:     }
123:
124:     private void setBalanceSafely(String newBal) {
125:         // Called by lookupThread, but can be safely
126:         // called by any thread.
127:         final String newBalance = newBal;
128:
129:         Runnable r = new Runnable() {
130:                 public void run() {
131:                     try {
132:                         setBalance(newBalance);
133:                     } catch (Exception x) {
134:                         x.printStackTrace();
135:                     }
136:                 }
137:             };
138:        
139:         SwingUtilities.invokeLater(r);
140:     }
141:
142:     private void setBalance(String newBalance) {
143:         // better be called by event thread!
144:         ensureEventThread();
145:
146:         balanceL.setText(newBalance);
147:         cancelB.setEnabled(false);
148:         searchB.setEnabled(true);
149:     }
150:
151:     private void cancelSearch() {
152:         // better be called by event thread!
153:         ensureEventThread();
154:
155:         cancelB.setEnabled(false); //prevent additional requests
156:
157:         if (lookupThread != null) {
158:             lookupThread.interrupt();
159:         }
160:     }
161:
162:     private void ensureEventThread() {
163:         // throws an exception if not invoked by the
164:         // event thread.
165:         if (SwingUtilities.isEventDispatchThread()) {
166:             return;
167:         }
168:
169:         throw new RuntimeException(only the event +
170:             thread should invoke this method);
171:     }
172:
173:     public static void main(String[] args) {
174:         BalanceLookup bl = new BalanceLookup();
175:
176:         JFrame f = new JFrame(Balance Lookup);
177:         f.addWindowListener(new WindowAdapter() {
178:                 public void windowClosing(WindowEvent e) {
179:                     System.exit(0);
180:                 }
181:             });
182:
183:         f.setContentPane(bl);
184:         f.setSize(400, 150);
185:         f.setVisible(true);
186:     }
187: }
The code for BalanceLookup is based on BalanceLookupCantCancel but includes a few key changes to support a worker thread. Now, when the Search button is clicked and the search() method is called, lookupAsync() is invoked instead of looking up the balance directly.
The event thread invokes lookupAsync() (lines 92107), passing in the account number and PIN strings. A new Runnable is created (lines 98103). Inside the run() method, the slow lookupBalance() method is called. When lookupBalance() finally returns the balance, it is passed to the setBalanceSafely() method. A new Thread named lookupThread is constructed and started (lines 105106). The event thread is now free to handle other events and lookupThread takes care of searching for the account information.
This time, the lookupBalance() method (lines 109122) gets called by lookupThread instead of the event thread. lookupThread proceeds to sleep for 5 seconds to simulate the slow lookup on the server. If lookupThread is not interrupted while sleeping, it returns 1,234.56 for the balance (line 118). If it was interrupted, it returns SEARCH CANCELLED (line 120).
The String returned from lookupBalance() is taken by the lookupThread and passed to setBalanceSafely() (lines 124140). Inside setBalanceSafely() , a Runnable is created that calls setBalance() inside its run() method (lines 129137). This Runnable is passed to SwingUtilities.invokeLater() so that the event thread is the one that ultimately calls the setBalance() method.
Inside setBalance() (lines 142149), a check is done by calling ensureEventThread() to be sure that it is indeed the event thread that has called the method. If it is, the balance label is updated with the information, the Cancel Search button is disabled again, and the Search button is enabled again.
The cancelSearch() method (lines 151160) is called by the event thread when the Cancel Search button is clicked. Inside, it disables the Cancel Search button and interrupts lookupThread . This causes lookupThread to throw an InterruptedException and return the SEARCH CANCELLED message.
The ensureEventThread() method (lines 162171) checks to see if the current thread is the event thread by using the SwingUtilities.isEventDispatchThread() method. If it is not, a RuntimeException is thrown. Several methods in BalanceLookup use ensureEventThread() to make sure that only the event thread is allowed to proceed.
Figure 9.4 shows how BalanceLookup looks just after startup. Notice that the Cancel Search button is disabled and that the balance label is BALANCE UNKNOWN .
Figure 9.4: BalanceLookup just after startup.
After an account number and PIN are entered and the Search button is clicked, the application window looks like Figure 9.5. Notice that the Search button is disabled, the Cancel Search button is enabled, and the balance label is SEARCHING ... . It remains like this for about 5 seconds while the lookup is simulated.
Figure 9.5: BalanceLookup after the Search button is clicked.
When the search finally completes, the application looks like Figure 9.6. Notice that the balance label is 1,234.56 (the fake balance), the Search button is enabled again, and the Cancel Search button is disabled again.
Figure 9.6: BalanceLookup after the search has completed.
If you click on the Cancel Search button during the 5 seconds while the search is in progress, the window looks like Figure 9.7. Notice that that the balance label is SEARCH CANCELLED , indicating that the search did not get a chance to complete. As before, the Search button is enabled, and the Cancel Search button is disabled.
Figure9.7: BalanceLookup after the Cancel Search button is clicked during a search.
  Tip Rather than spawning a new thread every time the Search button is clicked, a better design would be to have a thread up and running and waiting to do the work. The event thread would gather the information, pass it to the waiting worker thread using synchronization, and signal the worker through the wait-notify mechanism that a new request was pending. When the worker thread had fulfilled the request, it would update the GUI through the invokeLater() mechanism. The worker would then go back to waiting for another notification. To simplify the synchronization and notification of the handoff , an ObjectFIFO with a capacity of 1 could be used (see Chapter 18, First-In-First-Out (FIFO) Queue ). Also look at the thread-pooling techniques in Chapter 13, Thread Pooling, for an example of how to do this type of handoff from one thread to another through a First-In-First-Out queue.

Toc


Java Thread Programming
Java Thread Programming
ISBN: 0672315858
EAN: 2147483647
Year: 2005
Pages: 149
Authors: Paul Hyde

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net