Adding Multithreading Capability to Your Java Applications
- What Are Threads?
- Interrupting Threads
- Thread Properties
- Thread Priorities
- Selfish Threads
- Synchronization
- Deadlocks
- User Interface Programming with Threads
- Using Pipes for Communication between Threads
You are probably familiar with multitasking: the ability to have more than one program working at what seems like the same time. For example, you can print while editing or sending a fax. Of course, unless you have a multiple-processor machine, what is really going on is that the operating system is doling out resources to each program, giving the impression of parallel activity. This resource distribution is possible because while you may think you are keeping the computer busy by, for example, entering data, most of the CPU's time will be idle. (A fast typist takes around 1/20 of a second per character typed, after all, which is a huge time interval for a computer.)
Multitasking can be done in two ways, depending on whether the operating system interrupts programs without consulting with them first, or whether pro-grams are only interrupted when they are willing to yield control. The former is called preemptive multitasking; the latter is called cooperative (or, simply, nonpreemptive) multitasking. Windows 3.1 and Mac OS 9 are cooperative multitasking systems, and UNIX/Linux, Windows NT (and Windows 95 for 32-bit programs), and OS X are preemptive. (Although harder to implement, preemptive multitasking is much more effective. With cooperative multitasking, a badly behaved program can hog everything.)
Multithreaded programs extend the idea of multitasking by taking it one level lower: individual programs will appear to do multiple tasks at the same time. Each task is usually called a threadwhich is short for thread of control. Programs that can run more than one thread at once are said to be multithreaded. Think of each thread as running in a separate context: contexts make it seem as though each thread has its own CPUwith registers, memory, and its own code.
So, what is the difference between multiple processes and multiple threads? The essential difference is that while each process has a complete set of its own variables, threads share the same data. This sounds somewhat risky, and indeed it can be, as you will see later in this chapter. But it takes much less overhead to create and destroy individual threads than it does to launch new processes, which is why all modern operating systems support multithreading. Moreover, inter-process communication is much slower and more restrictive than communication between threads.
Multithreading is extremely useful in practice. For example, a browser should be able to simultaneously download multiple images. An email program should let you read your email while it is downloading new messages. The Java programming language itself uses a thread to do garbage collection in the backgroundthus saving you the trouble of managing memory! Graphical user interface (GUI) programs have a separate thread for gathering user interface events from the host operating environment. This chapter shows you how to add multithreading capability to your Java applications and applets.
Fair warning: multithreading can get very complex. In this chapter, we present all of the tools that the Java programming language provides for thread programming. We explain their use and limitations and give some simple but typical examples. However, for more intricate situations, we suggest that you turn to a more advanced reference, such as Concurrent Programming in Java by Doug Lea [Addison-Wesley 1999].
NOTE
In many programming languages, you have to use an external thread package to do multithreaded programming. The Java programming language builds in multithreading, which makes your job much easier.
What Are Threads?
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 will 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 11.)
As soon as you click on 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:
public void addBall() { try { Ball b = new Ball(canvas); canvas.add(b); for (int i = 1; i <= 1000; i++) { b.move(); Thread.sleep(5); } } catch (InterruptedException exception) { } }
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 canvas. The static sleep method of the Thread class pauses for 5 milliseconds.
Figure 11: Using a thread to animate a bouncing ball
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 will 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 bounces and click on the "Close" button, the ball continues bouncing anyway. You cannot interact with the program until the ball has finished bouncing.
NOTE
If you carefully look over the code at the end of this section, you will notice the call
canvas.paint(canvas.getGraphics())
inside the move method of the Ball class. That is pretty strangenormally, you'd call repaint and let the AWT worry about getting the graphics context and doing the painting. But if you try to call canvas.repaint() in this program, you'll find out that the canvas is never repainted since the addBall method has completely taken over all processing. In the next program, where we use a separate thread to compute the ball position, we'll again use the familiar repaint.
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 on a "Stop" or "Back" button to interrupt the loading process. In the next section, we will show you how to keep the user in control by running crucial parts of the code in a separate thread.
Example 11 is the entire code for the program.
Example 11: Bounce.java
1. 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.show(); 17. } 18. } 19. 20. /** 21. The frame with canvas and buttons. 22. */ 23. class BounceFrame extends JFrame 24. { 25. /** 26. Constructs the frame with the canvas for showing the 27. bouncing ball and Start and Close buttons 28. */ 29. public BounceFrame() 30. { 31. setSize(WIDTH, HEIGHT); 32. setTitle("Bounce"); 33. 34. Container contentPane = getContentPane(); 35. canvas = new BallCanvas(); 36. contentPane.add(canvas, BorderLayout.CENTER); 37. JPanel buttonPanel = new JPanel(); 38. addButton(buttonPanel, "Start", 39. new ActionListener() 40. { 41. public void actionPerformed(ActionEvent evt) 42. { 43. addBall(); 44. } 45. }); 46. 47. addButton(buttonPanel, "Close", 48. new ActionListener() 49. { 50. public void actionPerformed(ActionEvent evt) 51. { 52. System.exit(0); 53. } 54. }); 55. contentPane.add(buttonPanel, BorderLayout.SOUTH); 56. } 57. 58. /** 59. Adds a button to a container. 60. @param c the container 61. @param title the button title 62. @param listener the action listener for the button 63. */ 64. public void addButton(Container c, String title, 65. ActionListener listener) 66. { 67. JButton button = new JButton(title); 68. c.add(button); 69. button.addActionListener(listener); 70. } 71. 72. /** 73. Adds a bouncing ball to the canvas and makes 74. it bounce 1,000 times. 75. */ 76. public void addBall() 77. { 78. try 79. { 80. Ball b = new Ball(canvas); 81. canvas.add(b); 82. 83. for (int i = 1; i <= 1000; i++) 84. { 85. b.move(); 86. Thread.sleep(5); 87. } 88. } 89. catch (InterruptedException exception) 90. { 91. } 92. } 93. 94. private BallCanvas canvas; 95. public static final int WIDTH = 450; 96. public static final int HEIGHT = 350; 97. } 98. 99. /** 100. The canvas that draws the balls. 101. */ 102. class BallCanvas extends JPanel 103. { 104. /** 105. Add a ball to the canvas. 106. @param b the ball to add 107. */ 108. public void add(Ball b) 109. { 110. balls.add(b); 111. } 112. 113. public void paintComponent(Graphics g) 114. { 115. super.paintComponent(g); 116. Graphics2D g2 = (Graphics2D)g; 117. for (int i = 0; i < balls.size(); i++) 118. { 119. Ball b = (Ball)balls.get(i); 120. b.draw(g2); 121. } 122. } 123. 124. private ArrayList balls = new ArrayList(); 125. } 126. 127. /** 128. A ball that moves and bounces off the edges of a 129. component 130. */ 131. class Ball 132. { 133. /** 134. Constructs a ball in the upper left corner 135. @c the component in which the ball bounces 136. */ 137. public Ball(Component c) { canvas = c; } 138. 139. /** 140. Draws the ball at its current position 141. @param g2 the graphics context 142. */ 143. public void draw(Graphics2D g2) 144. { 145. g2.fill(new Ellipse2D.Double(x, y, XSIZE, YSIZE)); 146. } 147. 148. /** 149. Moves the ball to the next position, reversing direction 150. if it hits one of the edges 151. */ 152. public void move() 153. { 154. x += dx; 155. y += dy; 156. if (x < 0) 157. { 158. x = 0; 159. dx = -dx; 160. } 161. if (x + XSIZE >= canvas.getWidth()) 162. { 163. x = canvas.getWidth() - XSIZE; 164. dx = -dx; 165. } 166. if (y < 0) 167. { 168. y = 0; 169. dy = -dy; 170. } 171. if (y + YSIZE >= canvas.getHeight()) 172. { 173. y = canvas.getHeight() - YSIZE; 174. dy = -dy; 175. } 176. 177. canvas.paint(canvas.getGraphics()); 178. } 179. 180. private Component canvas; 181. private static final int XSIZE = 15; 182. private static final int YSIZE = 15; 183. private int x = 0; 184. private int y = 0; 185. private int dx = 2; 186. private int dy = 2; 187. }
java.lang.Thread
static void sleep(long millis)
sleeps for the given number of millisecondParameters:
millis
the number of milliseconds to sleep
In the previous sections, you learned what is required to split a program into multiple concurrent tasks. Each task needs to be placed into a run method of a class that extends Thread. But what if we want to add the run method to a class that already extends another class? This occurs most often when we want to add multithreading to an applet. An applet class already inherits from JApplet, and we cannot inherit from two parent classes, so we need to use an interface. The necessary interface is built into the Java platform. It is called Runnable. We take up this important interface next.
Using Threads to Give Other Tasks a Chance
We will make our bouncing-ball program more responsive by running the code that moves the ball in a separate thread.
NOTE
Since most computers do not have multiple processors, the Java virtual machine (JVM) uses a mechanism in which each thread gets a chance to run for a little while, then activates another thread. The virtual machine generally relies on the host operating system to provide the thread scheduling package.
Our next program uses two threads: one for the bouncing ball and another for the event dispatch thread that takes care of user interface events. Because each thread gets a chance to run, the main thread has the opportunity to notice when you click on the "Close" button while the ball is bouncing. It can then process the "close" action.
There is a simple procedure for running code in a separate thread: place the code into the run method of a class derived from Thread.
To make our bouncing-ball program into a separate thread, we need only derive a class BallThread from Thread and place the code for the animation inside the run method, as in the following code:
class BallThread extends Thread { . . . public void run() { try { for (int i = 1; i <= 1000; i++) { b.move(); sleep(5); } } catch (InterruptedException exception) { } } . . . }
You may have noticed that we are catching an exception called Interrupted-Exception. Methods such as sleep and wait throw this exception when your thread is interrupted because another thread has called the interrupt method. Interrupting a thread is a very drastic way of getting the thread's attention, even when it is not active. Typically, a thread is interrupted to terminate it. Accordingly, our run method exits when an InterruptedException occurs.
Running and Starting Threads
When you construct an object derived from Thread, the run method is not automatically called.
BallThread thread = new BallThread(. . .); // won't run yet
You must call the start method in your object to actually start a thread.
thread.start();
CAUTION
Do not call the run method directlystart will call it when the thread is set up and ready to go. Calling the run method directly merely executes its contents in the same threadno new thread is started.
Beginners are sometimes misled into believing that every method of a Thread object automatically runs in a new thread. As you have seen, that is not true. The methods of any object (whether a Thread object or not) run in whatever thread they are called. A new thread is only started by the start method. That new thread then executes the run method.
In the Java programming language, a thread needs to tell the other threads when it is idle, so the other threads can grab the chance to execute the code in their run procedures. (See Figure 12.) The usual way to do this is through the static sleep method. The run method of the BallThread class uses the call to sleep(5) to indicate that the thread will be idle for the next five milliseconds. After five milliseconds, it will start up again, but in the meantime, other threads have a chance to get work done.
TIP
There are a number of static methods in the Thread class. They all operate on the current thread, that is, the thread that executes the method. For example, the static sleep method idles the thread that is calling sleep.
Figure 12: The Event Dispatch and Ball Threads
The complete code is shown in Example 12.
Example 12: BounceThread.java
1. 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 running in a separate thread 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.show(); 17. } 18. } 19. 20. /** 21. The frame with canvas and buttons. 22. */ 23. class BounceFrame extends JFrame 24. { 25. /** 26. Constructs the frame with the canvas for showing the 27. bouncing ball and Start and Close buttons 28. */ 29. public BounceFrame() 30. { 31. setSize(WIDTH, HEIGHT); 32. setTitle("BounceThread"); 33. 34. Container contentPane = getContentPane(); 35. canvas = new BallCanvas(); 36. contentPane.add(canvas, BorderLayout.CENTER); 37. JPanel buttonPanel = new JPanel(); 38. addButton(buttonPanel, "Start", 39. new ActionListener() 40. { 41. public void actionPerformed(ActionEvent evt) 42. { 43. addBall(); 44. } 45. }); 46. 47. addButton(buttonPanel, "Close", 48. new ActionListener() 49. { 50. public void actionPerformed(ActionEvent evt) 51. { 52. System.exit(0); 53. } 54. }); 55. contentPane.add(buttonPanel, BorderLayout.SOUTH); 56. } 57. 58. /** 59. Adds a button to a container. 60. @param c the container 61. @param title the button title 62. @param listener the action listener for the button 63. */ 64. public void addButton(Container c, String title, 65. ActionListener listener) 66. { 67. JButton button = new JButton(title); 68. c.add(button); 69. button.addActionListener(listener); 70. } 71. 72. /** 73. Adds a bouncing ball to the canvas and starts a thread 74. to make it bounce 75. */ 76. public void addBall() 77. { 78. Ball b = new Ball(canvas); 79. canvas.add(b); 80. BallThread thread = new BallThread(b); 81. thread.start(); 82. } 83. 84. private BallCanvas canvas; 85. public static final int WIDTH = 450; 86. public static final int HEIGHT = 350; 87. } 88. 89. /** 90. A thread that animates a bouncing ball. 91. */ 92. class BallThread extends Thread 93. { 94. /** 95. Constructs the thread. 96. @aBall the ball to bounce 97. */ 98. public BallThread(Ball aBall) { b = aBall; } 99. 100. public void run() 101. { 102. try 103. { 104. for (int i = 1; i <= 1000; i++) 105. { 106. b.move(); 107. sleep(5); 108. } 109. } 110. catch (InterruptedException exception) 111. { 112. } 113. } 114. 115. private Ball b; 116. } 117. 118. /** 119. The canvas that draws the balls. 120. */ 121. class BallCanvas extends JPanel 122. { 123. /** 124. Add a ball to the canvas. 125. @param b the ball to add 126. */ 127. public void add(Ball b) 128. { 129. balls.add(b); 130. } 131. 132. public void paintComponent(Graphics g) 133. { 134. super.paintComponent(g); 135. Graphics2D g2 = (Graphics2D)g; 136. for (int i = 0; i < balls.size(); i++) 137. { 138. Ball b = (Ball)balls.get(i); 139. b.draw(g2); 140. } 141. } 142. 143. private ArrayList balls = new ArrayList(); 144. } 145. 146. /** 147. A ball that moves and bounces off the edges of a 148. component 149. */ 150. class Ball 151. { 152. /** 153. Constructs a ball in the upper left corner 154. @c the component in which the ball bounces 155. */ 156. public Ball(Component c) { canvas = c; } 157. 158. /** 159. Draws the ball at its current position 160. @param g2 the graphics context 161. */ 162. public void draw(Graphics2D g2) 163. { 164. g2.fill(new Ellipse2D.Double(x, y, XSIZE, YSIZE)); 165. } 166. 167. /** 168. Moves the ball to the next position, reversing direction 169. if it hits one of the edges 170. */ 171. public void move() 172. { 173. x += dx; 174. y += dy; 175. if (x < 0) 176. { 177. x = 0; 178. dx = -dx; 179. } 180. if (x + XSIZE >= canvas.getWidth()) 181. { 182. x = canvas.getWidth() - XSIZE; 183. dx = -dx; 184. } 185. if (y < 0) 186. { 187. y = 0; 188. dy = -dy; 189. } 190. if (y + YSIZE >= canvas.getHeight()) 191. { 192. y = canvas.getHeight() - YSIZE; 193. dy = -dy; 194. } 195. 196. canvas.repaint(); 197. } 198. 199. private Component canvas; 200. private static final int XSIZE = 15; 201. private static final int YSIZE = 15; 202. private int x = 0; 203. private int y = 0; 204. private int dx = 2; 205. private int dy = 2; 206. } 207.
java.lang.Thread
Thread()
constructs a new thread. You must start the thread to activate its run method.void run()
You must override this function and add the code that you want to have executed in the thread.void start()
starts this thread, causing the run() method to be called. This method will return immediately. The new thread runs concurrently.
Running Multiple Threads
Run the program in the preceding section. Now, click on the "Start" button again while a ball is running. Click on it a few more times. You will see a whole bunch of balls bouncing away, as captured in Figure 13. Each ball will move 1,000 times until it comes to its final resting place.
Figure 13: Multiple threads
This example demonstrates a great advantage of the thread architecture in the Java programming language. It is very easy to create any number of autonomous objects that appear to run in parallel.
Occasionally, you may want to enumerate the currently running threadssee the API note in the "Thread Groups" section for details.
The Runnable Interface
We could have saved ourselves a class by having the Ball class extend the Thread class. As an added advantage of that approach, the run method has access to the private fields of the Ball class:
class Ball extends Thread { public void run() { try { for (int i = 1; i <= 1000; i++) { x += dx; y += dy; . . . canvas.repaint(); sleep(5); } } catch (InterruptedException exception) { } } . . . private Component canvas; private int x = 0; private int y = 0; private int dx = 2; private int dy = 2; }
Conceptually, of course, this is dubious. A ball isn't a thread, so inheritance isn't really appropriate. Nevertheless, programmers sometimes follow this approach when the run method of a thread needs to access private fields of another class. In the preceding section, we've avoided that issue altogether by having the run method call only public methods of the Ball class, but it isn't always so easy to do that.
Suppose the run method needs access to private fields, but the class into which you want to put the run method already has another superclass. Then it can't extend the Thread class, but you can make the class implement the Runnable interface. As though you had derived from Thread, put the code that needs to run in the run method. For example,
class Animation extends JApplet implements Runnable { . . . public void run() { // thread action goes here } }
You still need to make a thread object to launch the thread. Give that thread a reference to the Runnable object in its constructor. The thread then calls the run method of that object.
class Animation extends JApplet implements Runnable { . . . public void start() { runner = new Thread(this); runner.start(); } . . . private Thread runner; }
In this case, the this argument to the Thread constructor specifies that the object whose run method should be called when the thread executes is an instance of the Animation object.
Some people even claim that you should always follow this approach and never subclass the Thread class. That advice made sense for Java 1.0, before inner classes were invented, but it is now outdated. If the run method of a thread needs private access to another class, you can often use an inner class, like this:
class Animation extends JApplet { . . . public void start() { runner = new Thread() { public void run() { // thread action goes here } }; runner.start(); } . . . private Thread runner; }
A plausible use for the Runnable interface would be a thread pool in which pre-spawned threads are kept around for running. Thread pools are sometimes used in environments that execute huge numbers of threads, to reduce the cost of creating and garbage collecting thread objects.
java.lang.Thread
Thread(Runnable target)
constructs a new thread that calls the run() method of the specified target.
java.lang.Thread
void run()
You must override this method and place in the thread the code that you want to have executed.