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."
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.
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
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.
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().
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.
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
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 Thread
s and ThreadGroup
s.
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 Thread
s and other ThreadGroup
s. These other ThreadGroup
s can, in turn, contain other Thread
s and ThreadGroup
s. 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 ThreadGroup
s 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.
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
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."
3.147.58.196