|
Let us start by looking at a program that does not use multiple threads and that, as a consequence, makes it difficult for the user to perform several tasks with that program. After we dissect it, we then show you how easy it is to have this program run separate threads. This program animates a bouncing ball by continually moving the ball, finding out if it bounces against a wall, and then redrawing it. (See Figure 1-1.) Figure 1-1. Using threads to animate bouncing ballsAs soon as you click the Start button, the program launches a ball from the upper-left corner of the screen and the ball begins bouncing. The handler of the Start button calls the addBall method. That method contains a loop running through 1,000 moves. Each call to move moves the ball by a small amount, adjusts the direction if it bounces against a wall, and then redraws the panel. Ball ball = new Ball(); panel.add(ball); for (int i = 1; i <= STEPS; i++) { ball.move(panel.getBounds()); panel.paint(panel.getGraphics()); Thread.sleep(DELAY); } The static sleep method of the Thread class pauses for the given number of milliseconds. The call to Thread.sleep does not create a new threadsleep is a static method of the THRead class that temporarily stops the activity of the current thread. The sleep method can throw an InterruptedException. We discuss this exception and its proper handling later. For now, we simply terminate the bouncing if this exception occurs. If you run the program, the ball bounces around nicely, but it completely takes over the application. If you become tired of the bouncing ball before it has finished its 1,000 moves and click the Close button, the ball continues bouncing anyway. You cannot interact with the program until the ball has finished bouncing. NOTE
Obviously, the behavior of this program is rather poor. You would not want the programs that you use behaving in this way when you ask them to do a time-consuming job. After all, when you are reading data over a network connection, it is all too common to be stuck in a task that you would really like to interrupt. For example, suppose you download a large image and decide, after seeing a piece of it, that you do not need or want to see the rest; you certainly would like to be able to click a Stop or Back button to interrupt the loading process. In the next section, we show you how to keep the user in control by running crucial parts of the code in a separate thread. Example 1-1 shows the code for the program. Example 1-1. Bounce.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.geom.*; 4. import java.util.*; 5. import javax.swing.*; 6. 7. /** 8. Shows an animated bouncing ball. 9. */ 10. public class Bounce 11. { 12. public static void main(String[] args) 13. { 14. JFrame frame = new BounceFrame(); 15. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 16. frame.setVisible(true); 17. } 18. } 19. 20. /** 21. A ball that moves and bounces off the edges of a 22. rectangle 23. */ 24. class Ball 25. { 26. /** 27. Moves the ball to the next position, reversing direction 28. if it hits one of the edges 29. */ 30. public void move(Rectangle2D bounds) 31. { 32. x += dx; 33. y += dy; 34. if (x < bounds.getMinX()) 35. { 36. x = bounds.getMinX(); 37. dx = -dx; 38. } 39. if (x + XSIZE >= bounds.getMaxX()) 40. { 41. x = bounds.getMaxX() - XSIZE; 42. dx = -dx; 43. } 44. if (y < bounds.getMinY()) 45. { 46. y = bounds.getMinY(); 47. dy = -dy; 48. } 49. if (y + YSIZE >= bounds.getMaxY()) 50. { 51. y = bounds.getMaxY() - YSIZE; 52. dy = -dy; 53. } 54. } 55. 56. /** 57. Gets the shape of the ball at its current position. 58. */ 59. public Ellipse2D getShape() 60. { 61. return new Ellipse2D.Double(x, y, XSIZE, YSIZE); 62. } 63. 64. private static final int XSIZE = 15; 65. private static final int YSIZE = 15; 66. private double x = 0; 67. private double y = 0; 68. private double dx = 1; 69. private double dy = 1; 70. } 71. 72. /** 73. The panel that draws the balls. 74. */ 75. class BallPanel extends JPanel 76. { 77. /** 78. Add a ball to the panel. 79. @param b the ball to add 80. */ 81. public void add(Ball b) 82. { 83. balls.add(b); 84. } 85. 86. public void paintComponent(Graphics g) 87. { 88. super.paintComponent(g); 89. Graphics2D g2 = (Graphics2D) g; 90. for (Ball b : balls) 91. { 92. g2.fill(b.getShape()); 93. } 94. } 95. 96. private ArrayList<Ball> balls = new ArrayList<Ball>(); 97. } 98. 99. /** 100. The frame with panel and buttons. 101. */ 102. class BounceFrame extends JFrame 103. { 104. /** 105. Constructs the frame with the panel for showing the 106. bouncing ball and Start and Close buttons 107. */ 108. public BounceFrame() 109. { 110. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 111. setTitle("Bounce"); 112. 113. panel = new BallPanel(); 114. add(panel, BorderLayout.CENTER); 115. JPanel buttonPanel = new JPanel(); 116. addButton(buttonPanel, "Start", 117. new ActionListener() 118. { 119. public void actionPerformed(ActionEvent event) 120. { 121. addBall(); 122. } 123. }); 124. 125. addButton(buttonPanel, "Close", 126. new ActionListener() 127. { 128. public void actionPerformed(ActionEvent event) 129. { 130. System.exit(0); 131. } 132. }); 133. add(buttonPanel, BorderLayout.SOUTH); 134. } 135. 136. /** 137. Adds a button to a container. 138. @param c the container 139. @param title the button title 140. @param listener the action listener for the button 141. */ 142. public void addButton(Container c, String title, ActionListener listener) 143. { 144. JButton button = new JButton(title); 145. c.add(button); 146. button.addActionListener(listener); 147. } 148. 149. /** 150. Adds a bouncing ball to the panel and makes 151. it bounce 1,000 times. 152. */ 153. public void addBall() 154. { 155. try 156. { 157. Ball ball = new Ball(); 158. panel.add(ball); 159. 160. for (int i = 1; i <= STEPS; i++) 161. { 162. ball.move(panel.getBounds()); 163. panel.paint(panel.getGraphics()); 164. Thread.sleep(DELAY); 165. } 166. } 167. catch (InterruptedException e) 168. { 169. } 170. } 171. 172. private BallPanel panel; 173. public static final int DEFAULT_WIDTH = 450; 174. public static final int DEFAULT_HEIGHT = 350; 175. public static final int STEPS = 1000; 176. public static final int DELAY = 3; 177. } java.lang.Thread 1.0
Using Threads to Give Other Tasks a ChanceWe will make our bouncing-ball program more responsive by running the code that moves the ball in a separate thread. In fact, you will be able to launch multiple balls. Each of them is moved by its own thread. In addition, the AWT event dispatch thread continues running in parallel, taking care of user interface events. Because each thread gets a chance to run, the main thread has the opportunity to notice when a user clicks the Close button while the balls are bouncing. The thread can then process the "close" action. Here is a simple procedure for running a task in a separate thread:
To make our bouncing-ball program into a separate thread, we need only implement a class BallRunnable and place the code for the animation inside the run method, as in the following code: class BallRunnable implements Runnable { . . . public void run() { try { for (int i = 1; i <= STEPS; i++) { ball.move(component.getBounds()); component.repaint(); Thread.sleep(DELAY); } } catch (InterruptedException exception) { } } . . . } Again, we need to catch an InterruptedException that the sleep method threatens to throw. We discuss this exception in the next section. Typically, a thread is terminated by being interrupted. Accordingly, our run method exits when an InterruptedException occurs. Whenever the Start button is clicked, the addBall method launches a new thread (see Figure 1-2): Ball b = new Ball(); panel.add(b); Runnable r = new BallRunnable(b, panel); Thread t = new Thread(r); t.start(); Figure 1-2. Running multiple threadsThat's all there is to it! You now know how to run tasks in parallel. The remainder of this chapter tells you how to control the interaction between threads. The complete code is shown in Example 1-2. NOTE
CAUTION
Example 1-2. BounceThread.java1. import java.awt.*; 2. import java.awt.event.*; 3. import java.awt.geom.*; 4. import java.util.*; 5. import javax.swing.*; 6. 7. /** 8. Shows an animated bouncing ball. 9. */ 10. public class BounceThread 11. { 12. public static void main(String[] args) 13. { 14. JFrame frame = new BounceFrame(); 15. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 16. frame.setVisible(true); 17. } 18. } 19. 20. /** 21. A runnable that animates a bouncing ball. 22. */ 23. class BallRunnable implements Runnable 24. { 25. /** 26. Constructs the runnable. 27. @aBall the ball to bounce 28. @aPanel the component in which the ball bounces 29. */ 30. public BallRunnable(Ball aBall, Component aComponent) 31. { 32. ball = aBall; 33. component = aComponent; 34. } 35. 36. public void run() 37. { 38. try 39. { 40. for (int i = 1; i <= STEPS; i++) 41. { 42. ball.move(component.getBounds()); 43. component.repaint(); 44. Thread.sleep(DELAY); 45. } 46. } 47. catch (InterruptedException e) 48. { 49. } 50. } 51. 52. private Ball ball; 53. private Component component; 54. public static final int STEPS = 1000; 55. public static final int DELAY = 5; 56. } 57. 58. /** 59. A ball that moves and bounces off the edges of a 60. rectangle 61. */ 62. class Ball 63. { 64. /** 65. Moves the ball to the next position, reversing direction 66. if it hits one of the edges 67. */ 68. public void move(Rectangle2D bounds) 69. { 70. x += dx; 71. y += dy; 72. if (x < bounds.getMinX()) 73. { 74. x = bounds.getMinX(); 75. dx = -dx; 76. } 77. if (x + XSIZE >= bounds.getMaxX()) 78. { 79. x = bounds.getMaxX() - XSIZE; 80. dx = -dx; 81. } 82. if (y < bounds.getMinY()) 83. { 84. y = bounds.getMinY(); 85. dy = -dy; 86. } 87. if (y + YSIZE >= bounds.getMaxY()) 88. { 89. y = bounds.getMaxY() - YSIZE; 90. dy = -dy; 91. } 92. } 93. 94. /** 95. Gets the shape of the ball at its current position. 96. */ 97. public Ellipse2D getShape() 98. { 99. return new Ellipse2D.Double(x, y, XSIZE, YSIZE); 100. } 101. 102. private static final int XSIZE = 15; 103. private static final int YSIZE = 15; 104. private double x = 0; 105. private double y = 0; 106. private double dx = 1; 107. private double dy = 1; 108. } 109. 110. /** 111. The panel that draws the balls. 112. */ 113. class BallPanel extends JPanel 114. { 115. /** 116. Add a ball to the panel. 117. @param b the ball to add 118. */ 119. public void add(Ball b) 120. { 121. balls.add(b); 122. } 123. 124. public void paintComponent(Graphics g) 125. { 126. super.paintComponent(g); 127. Graphics2D g2 = (Graphics2D) g; 128. for (Ball b : balls) 129. { 130. g2.fill(b.getShape()); 131. } 132. } 133. 134. private ArrayList<Ball> balls = new ArrayList<Ball>(); 135. } 136. 137. /** 138. The frame with panel and buttons. 139. */ 140. class BounceFrame extends JFrame 141. { 142. /** 143. Constructs the frame with the panel for showing the 144. bouncing ball and Start and Close buttons 145. */ 146. public BounceFrame() 147. { 148. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 149. setTitle("BounceThread"); 150. 151. panel = new BallPanel(); 152. add(panel, BorderLayout.CENTER); 153. JPanel buttonPanel = new JPanel(); 154. addButton(buttonPanel, "Start", 155. new ActionListener() 156. { 157. public void actionPerformed(ActionEvent event) 158. { 159. addBall(); 160. } 161. }); 162. 163. addButton(buttonPanel, "Close", 164. new ActionListener() 165. { 166. public void actionPerformed(ActionEvent event) 167. { 168. System.exit(0); 169. } 170. }); 171. add(buttonPanel, BorderLayout.SOUTH); 172. } 173. 174. /** 175. Adds a button to a container. 176. @param c the container 177. @param title the button title 178. @param listener the action listener for the button 179. */ 180. public void addButton(Container c, String title, ActionListener listener) 181. { 182. JButton button = new JButton(title); 183. c.add(button); 184. button.addActionListener(listener); 185. } 186. 187. /** 188. Adds a bouncing ball to the canvas and starts a thread 189. to make it bounce 190. */ 191. public void addBall() 192. { 193. Ball b = new Ball(); 194. panel.add(b); 195. Runnable r = new BallRunnable(b, panel); 196. Thread t = new Thread(r); 197. t.start(); 198. } 199. 200. private BallPanel panel; 201. public static final int DEFAULT_WIDTH = 450; 202. public static final int DEFAULT_HEIGHT = 350; 203. public static final int STEPS = 1000; 204. public static final int DELAY = 3; 205. } java.lang.Thread 1.0
java.lang.Runnable 1.0
|
|