Deitel & Associates, Inc. Logo

Back to www.deitel.com
digg.png delicious.png blinkit.png furl.png
Java How to Program, 6/e

ISBN:
0-13-148398-6
© 2005
pages: 1576
Buy the Book!
Amazon logo
InformIT logo

This four-part tutorial introduces multithreading in Java 5.0. You'll learn about the new thread-state model and the priority-based thread scheduler. We'll demonstrate how to create and execute threads by using the new classes Executors and ExecutorService from the java.util.concurrent package. You'll also learn how to create thread pools, which enable threads to be reused after they complete their tasks. This tutorial is intended for students who are already familiar with Java and for Java developers.
 
[Note: This series of four tutorials (Part 1, Part 2, Part 3, Part 4) is an excerpt (Sections 23.1-23.4) of Chapter 23, Multithreading, from our textbook Java How to Program, 6/e. These tutorials may refer to other chapters or sections of the book that are not included here. Permission Information: Deitel, Harvey M. and Paul J., JAVA HOW TO PROGRAM, ©2005, pp.1053-1058. Electronically reproduced by permission of Pearson Education, Inc., Upper Saddle River, New Jersey.]

23.4 Creating and Executing Threads

In J2SE 5.0, the preferred means of creating a multithreaded application is to implement the Runnable interface (package java.lang) and use built-in methods and classes to create Threads that execute the Runnables. The Runnable interface declares a single method named run. Runnables are executed by an object of a class that implements the Executor interface (package java.util.concurrent). This interface declares a single method named execute. An Executor object typically creates and manages a group of threads called a thread pool. These threads execute the Runnable objects passed to the execute method. The Executor assigns each Runnable to one of the available threads in the thread pool. If there are no available threads in the thread pool, the Executor creates a new thread or waits for a thread to become available and assigns that thread the Runnable that was passed to method execute. Depending on the Executor type, there may be a limit to the number of threads that can be created. Interface ExecutorService (package java.util.concurrent) is a subinterface of Executor that declares a number of other methods for managing the life cycle of the Executor. An object that implements the ExecutorService interface can be created using static methods declared in class Executors (package java.util.concurrent). We use these interfaces and methods in the next application, which executes three threads.

Class PrintTask (Fig. 23.4) implements Runnable (line 5), so that each PrintTask object can execute concurrently. Variable sleepTime (line 7) stores a random integer value (line 17) chosen when the PrintTask constructor executes. Each thread running a PrintTask object sleeps for the amount of time specified by the corresponding PrintTask object’s sleepTime, then outputs its name.

   1  // Fig. 23.4: PrintTask.java
2 // PrintTask class sleeps for a random time from 0 to 5 seconds
3 import java.util.Random;
4
5 class PrintTask implements Runnable
6 {
7 private int sleepTime; // random sleep time for thread
8 private String threadName; // name of thread
9 private static Random generator = new Random();
10
11 // assign name to thread
12 public PrintTask( String name )
13 {
14 threadName = name; // set name of thread
15
16 // pick random sleep time between 0 and 5 seconds
17 sleepTime = generator.nextInt( 5000 );
18 } // end PrintTask constructor
19
20 // method run is the code to be executed by new thread
21 public void run()
22 {
23 try // put thread to sleep for sleepTime amount of time
24 {
25 System.out.printf( "%s going to sleep for %d milliseconds.\n",
26 threadName, sleepTime );
27
28 Thread.sleep( sleepTime ); // put thread to sleep
29 } // end try
30 // if thread interrupted while sleeping, print stack trace
31 catch ( InterruptedException exception )
32 {
33 exception.printStackTrace();
34 } // end catch
35
36 // print thread name
37 System.out.printf( "%s done sleeping\n", threadName );
38 } // end method run
39 } // end class PrintTask<
 Fig. 23.4  Threads sleeping and awakening.

When a PrintTask is assigned to a processor for the first time, its run method (lines 21–38) begins execution. Lines 25–26 display a message indicating the name of the currently executing thread and stating that the thread is going to sleep for a certain number of milliseconds. Line 26 uses the threadName field which was initialized at line 14 with the PrintTask constructor’s argument. Line 28 invokes static method sleep of class Thread to place the thread into the timed waiting state. At this point, the thread loses the processor, and the system allows another thread to execute. When the thread awakens, it reenters the runnable state. When the PrintTask is assigned to a processor again, line 37 outputs the thread’s name in a message that indicates the thread is done sleeping—then method run terminates. Note that the catch at lines 31–34 is required because method sleep might throw an InterruptedException, which is a checked exception. Such an exception occurs if a sleeping thread’s interrupt method is called. Most programmers do not directly manipulate Thread objects, so InterruptedExceptions are unlikely to occur.

Figure 23.5 creates three threads of execution using the PrintTask class. Method main (lines 8–28) creates and names three PrintTask objects (lines 11–13). Line 18 creates a new ExecutorService. This line uses the newFixedThreadPool method of class Executors, which creates a pool consisting of a fixed number of Threads as indicated by the method’s argument (in this case, 3). These Threads are used by threadExecutor to execute the Runnables. If method execute is called and all the threads in the ExecutorService are being used, the Runnable will be placed in a queue and assigned to the first thread that completes its previous task. Executors method newCachedThreadPool returns an ExecutorService that creates new threads as they are needed by the application.

   1  // Fig. 23.5: RunnableTester.java
2 // Multiple threads printing at different intervals.
3 import java.util.concurrent.Executors;
4 import java.util.concurrent.ExecutorService;
5
6 public class RunnableTester
7 {
8 public static void main( String[] args )
9 {
10 // create and name each runnable
11 PrintTask task1 = new PrintTask( "thread1" );
12 PrintTask task2 = new PrintTask( "thread2" );
13 PrintTask task3 = new PrintTask( "thread3" );
14
15 System.out.println( "Starting threads" );
16
17 // create ExecutorService to manage threads
18 ExecutorService threadExecutor = Executors.newFixedThreadPool( 3 );
19
20 // start threads and place in runnable state
21 threadExecutor.execute( task1 ); // start task1
22 threadExecutor.execute( task2 ); // start task2
23 threadExecutor.execute( task3 ); // start task3
24
25 threadExecutor.shutdown(); // shutdown worker threads
26
27 System.out.println( "Threads started, main ends\n" );
28 } // end main
29 } // end class RunnableTester
 Fig. 23.5  Creates three PrintTasks and executes them.

 
Starting threads
Threads started, main ends

thread1 going to sleep for 1217 milliseconds
thread2 going to sleep for 3989 milliseconds
thread3 going to sleep for 662 milliseconds
thread3 done sleeping
thread1 done sleeping
thread2 done sleeping

 
Starting threads
thread1 going to sleep for 314 milliseconds
thread2 going to sleep for 1990 milliseconds
Threads started, main ends

thread3 going to sleep for 3016 milliseconds
thread1 done sleeping
thread2 done sleeping
thread3 done sleeping

Lines 21–23 invoke the ExecutorService’s execute method. This method creates a new Thread inside the ExecutorService to run the Runnable passed to it as an argument (in this case a PrintTask) and transitions that Thread from the new state to the runnable state. Method execute returns immediately from each invocation—the program does not wait for each PrintTask to finish. Line 25 calls ExecutorService method shutdown, which will end each Thread in threadExecutor as soon as each finishes executing its Runnable. Line 27 outputs a message indicating that the threads were started. [Note: Line 18 creates the ExecutorService using method newFixedThreadPool and the argument 3. This program executes only three Runnables, so a new Thread will be created by the ExecutorService for each Runnable. If the program executed more than three Runnables, additional Threads would not be created, but rather an existing Thread would be reused when it completed the Runnable assigned to it.]

The code in method main executes in the main thread. This thread is created by the JVM and executes the main method. The code in the run method of PrintTask (lines 21–38 of Fig. 23.4) executes in the threads created by the ExecutorService. When method main terminates (line 28), the program itself continues running because there are still threads that are alive (i.e., the threads started by threadExecutor that have not yet reached the terminated state). The program will not terminate until its last thread completes execution.

The sample outputs for this program show each thread’s name and sleep time as the thread goes to sleep. The thread with the shortest sleep time normally awakens first, indicates that it is done sleeping and terminates. In Section 23.8, we discuss multithreading issues that could prevent the thread with the shortest sleep time from awakening first. In the first output, the main thread terminates before any of the other threads output their names and sleep times. This shows that the main thread runs to completion before any of the other threads get a chance to run. In the second output, the first two threads output their names and sleep times before the main thread terminates. This shows that the operating system allowed other threads to execute before the main thread terminated. This is an example of the round-robin scheduling we discussed in Section 23.3.

Page 1 | 2 | 3 | 4

Tutorial Index