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.

Figure 23.4. Threads sleeping and awakening.

(This item is displayed on page 1060 in the print version)

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. ", 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 ", threadName ); 38 } // end method run 39 } // end class PrintTask

When a PrintTask is assigned to a processor for the first time, its run method (lines 2138) begins execution. Lines 2526 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 sleepingthen method run terminates. Note that the catch at lines 3134 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 828) creates and names three PrintTask objects (lines 1113). 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.

Figure 23.5. Creates three PrintTasks and executes them.

(This item is displayed on page 1061 in the print version)

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 " ); 28 } // end main 29 } // end class RunnableTester  

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 2123 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 invocationthe 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 2138 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.

Категории