Chapter 3. Creating and Starting a Thread

IN THIS CHAPTER

This chapter explores more of the Thread API, including getting a handle on the currently executing thread, thread naming, some of the different constructors, and putting a thread to sleep for awhile. You will use these features to enhance the TwoThread example from Chapter 2, "A Simple Two-Thread Example."

Using Thread.currentThread()

At times, it's useful to know which of the threads currently running in the Java Virtual Machine (JavaVM) is executing a segment of code. In multithreaded programming, more than one thread may enter a method and execute the statements within it.

In the TwoThread example from Chapter 2, two threads execute the code in the println() method of the PrintStream object referred to by the System.out reference:

System.out.println("Main thread");
System.out.println("New thread");

This one PrintStream object is set up by the JavaVM during initialization for use by any class at any time in a Java program through the System.out reference. Any running thread can invoke the println() method. In this case, the println() method does not care which thread invoked it; it simply takes the String passed in and prints it.

In the Thread API, you can use the static method

public static native Thread currentThread()

to determine which thread is executing a segment of code. You can use this information to take different actions within the code segment.

Note

Many of the methods in Thread are listed with some of the following modifiers: native, final, static, and synchronized. As a quick review, native methods are implemented in non-Java code (typically C or C++ in the JDK). Methods declared to be final may not be overridden in a subclass. When a method is static, it does not pertain to a particular instance of the class, but operates at the class level. The synchronized modifier guarantees that no more than one thread is allowed to execute the statements inside a method at one time. Later in this book, the synchronized modifier is explained in detail.

As an example, look at a new version of TwoThread in Listing 3.1. This example is a bit ridiculous for real-world applications, but serves to illustrate a use of Thread.currentThread().

Example 3.1. TwoThread.java—A Version of TwoThread That Uses currentThread()

 1: public class TwoThread extends Thread {
 2:     private Thread creatorThread;
 3:
 4:     public TwoThread() {
 5:         // make a note of the thread that constructed me!
 6:         creatorThread = Thread.currentThread();
 7:     }
 8:
 9:     public void run() {
10:         for ( int i = 0; i < 10; i++ ) {
11:             printMsg();
12:         }
13:     }
14:
15:     public void printMsg() {
16:         // get a reference to the thread running this
17:         Thread t = Thread.currentThread();
18:
19:         if ( t == creatorThread ) {
20:             System.out.println("Creator thread");
21:         } else if ( t == this ) {
22:            System.out.println("New thread");
23:         } else {
24:             System.out.println("Mystery thread —unexpected!");
25:         }
26:     }
27:
28:     public static void main(String[] args) {
29:         TwoThread tt = new TwoThread();
30:         tt.start();
31:
32:         for ( int i = 0; i < 10; i++ ) {
33:             tt.printMsg();
34:         }
35:     }
36: }

In this version, the System.out.println() statements have been removed from the loops and replaced by a call to the new printMsg() method (lines 11 and 33). This method does not take a String as a parameter, but instead determines which message to print, based on the thread that invokes it.

In the constructor (line 6), Thread.currentThread() is used to gain a reference to the Thread object for the thread that executed the new TwoThread(); statement.

This Thread reference is stored in the member variable creatorThread for later use.

To determine which message to print, printMsg() first gets a reference to the Thread that invoked it, by using the static method Thread.currentThread() (line 17). Next, it tests whether this reference t matches the creatorThread reference stored by the constructor. If so, it prints Creator thread (lines 19 and 20). If the reference doesn't match, printMsg() then checks whether t matches this. TwoThread IS-A Thread because it directly subclasses Thread. The this reference refers to the Thread object constructed on line 29 by the main thread. If the current thread equals this, New thread is printed (lines 21 and 22). Otherwise, another thread that was not accounted for invoked this method, and the message Mystery thread --unexpected! is printed (lines 23 and 24). In this example, the Mystery thread --unexpected! message will never be printed because only two threads will run this code and they have both been accounted for.

Listing 3.2 presents possible output from running TwoThread. Remember that the exact order of the messages printed, as well as how long each thread will run between context switches, depends on the thread scheduler. Therefore, your output might differ somewhat.

Example 3.2. Possible Output from TwoThread Using currentThread()

Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread
Creator thread
New thread

Naming a Thread: getName() and setName()

Every Thread has a name associated with it. If a name is not explicitly supplied, a default one is generated during construction. By name, you can differentiate between the various threads running in the JavaVM.

Using getName()

In the Thread API, the method

public final String getName()

is used to retrieve the current name. Listing 3.3 shows a new class, TwoThreadGetName, that uses the getName() method to differentiate between two running threads.

Example 3.3. TwoThreadGetName.java—Using getName()

 1: public class TwoThreadGetName extends Thread {
 2:     public void run() {
 3:         for ( int i = 0; i < 10; i++ ) {
 4:             printMsg();
 5:         }
 6:     }
 7:
 8:     public void printMsg() {
 9:         // get a reference to the thread running this
10:         Thread t = Thread.currentThread();
11:         String name = t.getName();
12:         System.out.println("name=" + name);
13:     }
14:
15:     public static void main(String[] args) {
16:         TwoThreadGetName tt = new TwoThreadGetName();
17:         tt.start();
18:
19:         for ( int i = 0; i < 10; i++ ) {
20:             tt.printMsg();
21:         }
22:     }
23: }

All printing occurs in printMsg() when it is invoked from the loop in run() (lines 3–5) and from the loop in main() (lines 19–21). First, in printMsg(), a reference to the currently executing Thread is obtained using Thread.currentThread() (line 10). Next, the name of this particular Thread is retrieved through getName() (line 11). Finally, the name is printed (line 12).

Listing 3.4 shows possible output from running TwoThreadGetName. Different output can (and usually does) occur each time this is run. For this particular run, note that the messages alternate between threads at first. However, about midway, three Thread-0 messages are printed consecutively without any messages from the other thread. At the end, the Thread-0 thread has died, and the main thread is able to catch up and print the backlogged messages.

This is a perfect example of the nondeterministic behavior of multithreaded programs—a critical issue for skilled Java developers to be aware of. In later chapters, you will learn techniques for ensuring the correctness of multithreaded programs—regardless of the order in which the threads happen to be scheduled to run.

Example 3.4. Possible Output from TwoThreadGetName

name=main
name=Thread-0
name=main
name=Thread-0
name=main
name=Thread-0
name=main
name=Thread-0
name=main
name=Thread-0
name=Thread-0
name=Thread-0
name=main
name=Thread-0
name=Thread-0
name=main
name=Thread-0
name=main
name=main
name=main

In addition to a thread named main, the JavaVM starts up other threads automatically. Table 3.1 lists the names of these threads on each of the three released Java platforms. Each row presents the various names of the same thread.

Table 3.1. Threads Started by the JavaVM

JDK 1.2 JDK 1.1 JDK 1.0
main main main
Finalizer Finalizer thread Finalizer thread
Reference Handler (none)(none)
Signal dispatcher (none)(none)
AWT-Windows AWT-Windows AWT-Win32
AWT-EventQueue-0 AWT-EventQueue-0 AWT-Callback-Win32
SunToolkit.PostEventQueue-0 (none)(none)
Screen Updater Screen Updater Screen Updater

Note that the Reference Handler, Signal dispatcher, and SunToolkit.PostEventQueue-0 threads are new to JDK 1.2. The threads named main and Finalizer (Reference Handler and Signal dispatcher for JDK 1.2) are started automatically for every application. The remaining threads are also started by the JavaVM when the application contains any graphical components from AWT or Swing. Therefore, in a JDK 1.2 application with a graphical user interface (GUI), eight threads are automatically started by the JavaVM.

As mentioned previously, the main thread is responsible for starting application execution by invoking the main() method. This is the thread from which most other developer-defined threads are spawned by application code. The Finalizer thread is used by the JavaVM to execute the finalize() method of objects just before they are garbage collected. The AWT-EventQueue-0 thread is more commonly known as the event thread and invokes event-handling methods such as actionPerformed(), keyPressed(), mouseClicked(), and windowClosing().

Using setName()

In the preceding example, the names associated with threads are their default names. You can explicitly specify the name of a Thread object by using the setName() method:

public final void setName(String newName)

The name of a thread is typically set before the thread is started, but setting the name of a thread already running is also permitted. Two Thread objects are permitted to have the same name, but you should avoid this for clarity. The main thread started by the JavaVM can also have its name changed, but this is also discouraged.

Tip

Although Java requires none of the following, it's good practice to follow these conventions when naming threads:

  • Invoke setName() on the Thread before start(), and do not rename the Thread after it is started.

  • Give each thread a brief, meaningful name when possible.

  • Give each thread a unique name.

  • Do not change the names of JavaVM threads, such as main.

Listing 3.5 shows a new class, TwoThreadSetName, that uses the setName() method to override the default thread name.

Example 3.5. TwoThreadSetName.java—Using setName()

 1: public class TwoThreadSetName extends Thread {
 2:     public void run() {
 3:         for ( int i = 0; i < 10; i++ ) {
 4:             printMsg();
 5:         }
 6:     }
 7:
 8:     public void printMsg() {
 9:         // get a reference to the thread running this
10:         Thread t = Thread.currentThread();
11:         String name = t.getName();
12:         System.out.println("name=" + name);
13:     }
14:
15:     public static void main(String[] args) {
16:         TwoThreadSetName tt = new TwoThreadSetName();
17:         tt.setName("my worker thread");
18:         tt.start();
19:
20:         for ( int i = 0; i < 10; i++ ) {
21:             tt.printMsg();
22:         }
23:     }
24: }

The only difference between TwoThreadSetName and TwoThreadGetName is the addition of the statement to set the name of the new thread to my worker thread (line 17) before the thread is started. The name of the main thread remains untouched. Listing 3.6 shows possible output; your output might differ because of nondeterministic thread scheduling.

Example 3.6. Possible Output from TwoThreadSetName

name=main
name=my worker thread
name=main
name=my worker thread
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=my worker thread
name=main
name=my worker thread
name=main
name=main

Thread Constructors

The central constructor in Thread is

public Thread(ThreadGroup group, Runnable target, String name)

The parameter name allows you to specify the name of the thread at construction time, rather than set it later using setName().

The parameter target refers to an object of type Runnable. This object's run() method will be invoked by the new thread instead of the run() method of Thread. Unlike a thread's name, if you're going to specify the target, you must do so at the time of construction. Chapter 4 explores in detail the issues involved in making a decision to use Runnable instead of extending Thread.

The parameter group lets you specify the ThreadGroup to which the new Thread will belong. Figure 3.1 shows the relationships among Threads and ThreadGroups.

A sample hierarchy of ThreadGroups and Threads.

Figure 3.1. A sample hierarchy of ThreadGroups and Threads.

An analogy can be drawn between the file system concept of directories and files and the ThreadGroup/Thread relationship. In a file system, a directory can contain files and other directories. These other directories, in turn, can contain other files and directories. Every file is in exactly one directory, which may itself be in another directory. Every directory except the one "root" or "base" directory is in another directory.

In Java, a ThreadGroup (much like a directory) can contain Threads and other ThreadGroups. These other ThreadGroups can, in turn, contain other Threads and ThreadGroups. Every Thread (much like a file) is a member of a ThreadGroup, which may itself be a member of another ThreadGroup.

If a ThreadGroup is not specified at the time of a Thread's construction, the ThreadGroup is inherited from the Thread that constructed it. Chapter 10, "Thread Groups," discusses ThreadGroups in more detail.

The Thread constructor used so far has been as follows:

public Thread()

By default, the ThreadGroup is that of the Thread that constructs it. No external Runnable is specified, so the Thread's own run() method is called. Because no name is specified, the name of the Thread will be automatically generated as something such as Thread-0.

The other constructors of Thread come somewhere in between the zero-argument constructor and the three-argument constructor. They specify some parameters of the three-argument constructor, and the nonspecified parameters take on their default values.

Enlivening a Thread: start() and isAlive()

The start() method signals the thread scheduler that this new thread is ready to go and should have its run() method invoked at the scheduler's earliest convenience. Because of the nondeterministic nature of multithreaded programming, one cannot be sure just when the thread will begin execution, but only that it should begin soon.

The API for start() is

public native synchronized void start()
            throws IllegalThreadStateException

If the Thread has already been started, an IllegalThreadStateException will be thrown. When the start() method of a Thread is invoked, the new thread is considered to come alive. The thread remains alive until the run() method returns or until the thread is abruptly stopped by the stop() method (which is a deprecated method, as of JDK 1.2!).

The method

public final native boolean isAlive()

can be used on Thread to test whether a thread has been started and is still running. Listing 3.7 shows an example of how isAlive() can be used.

Example 3.7. TwoThreadAlive.java—Using isAlive()

 1: public class TwoThreadAlive extends Thread {
 2:     public void run() {
 3:         for ( int i = 0; i < 10; i++ ) {
 4:             printMsg();
 5:         }
 6:     }
 7:
 8:     public void printMsg() {
 9:         // get a reference to the thread running this
10:         Thread t = Thread.currentThread();
11:         String name = t.getName();
12:         System.out.println("name=" + name);
13:     }
14:
15:     public static void main(String[] args) {
16:         TwoThreadAlive tt = new TwoThreadAlive();
17:         tt.setName("my worker thread");
18:
19:         System.out.println("before start(), tt.isAlive()=" +
                    [ccc]tt.isAlive());
20:         tt.start();
21:         System.out.println("just after start(), tt.isAlive()=" +
                    [ccc]tt.isAlive());
22:
23:         for ( int i = 0; i < 10; i++ ) {
24:             tt.printMsg();
25:         }
26:
27:         System.out.println(
28:             "at the end of main(), tt.isAlive()=" + tt.isAlive());
29:     }
30: }

The code on line 19 checks whether the new Thread object is alive. This will always be false because the thread has not yet been started. Immediately after the thread is started, another check is done (line 20). At this point, the new Thread object will always be alive. At the end of main(), one last check is done (lines 27 and 28). Sometimes, the Thread object will still be alive, and other times it will have already died, depending on the exact thread scheduling that occurs.

Listing 3.8 presents output from a particular run of TwoThreadAlive. Nearly every time this is run, it gives slightly different output. In this case, note that three messages are printed from the worker thread before the main thread has a chance to execute the isAlive() check right after start(). The worker thread keeps its lead and finishes its work—and is therefore no longer alive—well before the main is done, so the check at the end shows that the worker thread is no longer alive.

Example 3.8. Possible Output from TwoThreadAlive

before start(), tt.isAlive()=false
name=my worker thread
name=my worker thread
name=my worker thread
just after start(), tt.isAlive()=true
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=my worker thread
name=main
name=my worker thread
name=main
name=main
name=main
name=main
name=main
at the end of main(), tt.isAlive()=false

Using Thread.sleep()

As beneficial as it is for threads to go about doing their work as fast as possible, sometimes it would be useful if a thread could take a break and go to sleep for a while. In a clock application, it might be the case that the thread in charge of updating the displayed time should pause for 60 seconds at a time between the changing of the minutes displayed. A busy loop such as

long startTime = System.currentTimeMillis();
long stopTime = startTime + 60000;

while ( System.currentTimeMillis() < stopTime ) {
    // do nothing, but loop back
}

takes up a lot of processor cycles. Instead, it would be better to use the following static method on Thread

public static native void sleep(long msToSleep)
            throws InterruptedException

to wait for 60 seconds, like this:

try {
    Thread.sleep(60000);
} catch ( InterruptedException x ) {
    // ignore the exception
}

Sleeping is a much better option than using a busy loop. A sleeping thread does not use any processor cycles because its execution is suspended for the specified duration.

The sleep() method is static and puts only the currently executing thread—the one that would be returned by Thread.currentThread()—to sleep. It is not possible for a thread to put any other thread to sleep.

The try/catch construct is necessary because while a thread is sleeping, it might be interrupted by another thread. One thread might want to interrupt another to let it know that it should take some sort of action. Later chapters further explore the use of interrupts. Here, it suffices to say that a sleeping thread might be interrupted and will throw an InterruptedException if this occurs.

Listing 3.9 shows how sleep() can be used to slow down the action and how two threads may be inside the same method of one object at the same time.

Example 3.9. TwoThreadSleep.java—Using sleep()

 1: public class TwoThreadSleep extends Thread {
 2:     public void run() {
 3:         loop();
 4:     }
 5:
 6:     public void loop() {
 7:         // get a reference to the thread running this
 8:         Thread t = Thread.currentThread();
 9:         String name = t.getName();
10:
11:         System.out.println("just entered loop() - " + name);
12:
13:         for ( int i = 0; i < 10; i++ ) {
14:             try {
15:                 Thread.sleep(200);
16:             } catch ( InterruptedException x ) {
17:                 // ignore
18:             }
19:
20:             System.out.println("name=" + name);
21:         }
22:
23:         System.out.println("about to leave loop() - " + name);
24:     }
25: 
26:     public static void main(String[] args) {
27:         TwoThreadSleep tt = new TwoThreadSleep();
28:         tt.setName("my worker thread");
29:         tt.start();
30:
31:         // pause for a bit
32:         try {
33:             Thread.sleep(700);
34:         } catch ( InterruptedException x ) {
35:             // ignore
36:         }
37:
38:         tt.loop();
39:     }
40: }

The method loop() is used by both run() (line 3) and by main() (line 38) to print out all the messages. In main(), sleep() is used to delay the main thread's entry into the loop() method (lines 32–36). On lines 14–18, sleep() is also used to slow down the iterations through the for loop. Listing 3.10 shows sample output from a particular run.

Example 3.10. Sample Output from TwoThreadSleep

just entered loop() - my worker thread
name=my worker thread
name=my worker thread
just entered loop() - main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
name=main
name=my worker thread
about to leave loop() - my worker thread
name=main
name=main
name=main
about to leave loop() - main

In examining the output, you notice that both threads are inside the loop() method at the same time. Yet, each thread has its own copy of the local variable name to print its proper identification. Local variables work well with multiple threads, but accessing and modifying member variables (the state of an object) with multiple threads is tricky business. You will learn more about this in Chapter 7, "Concurrent Access to Objects and Variables."

Summary

This chapter begins to explore some of the API for Thread:

  • Thread.currentThread()

  • getName()

  • setName()

  • Thread(ThreadGroup, Runnable, String)

  • Thread()

  • start()

  • isAlive()

  • Thread.sleep()

The next chapters explain more of the API.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.58.196