Chapter 2. A Simple Two-Thread Example

IN THIS CHAPTER

This chapter shows just how simple it is to get a new thread up and running in a tiny Java application. The first thread is the "main" thread that is always spawned by the Java Virtual Machine (JavaVM) and starts an application. This main thread then spawns the second thread. Each of these threads will print its messages to the console to demonstrate that they both appear to be running simultaneously.

The steps to spawn a new thread in this chapter's example are

  • Extend the java.lang.Thread class.

  • Override the run() method in this subclass of Thread.

  • Create an instance of this new class.

  • Invoke the start() method on the instance.

Extending the java.lang.Thread Class

An instance of the java.lang.Thread class is associated with each thread running in the JavaVM. These Thread objects serve as the interface for interacting with the underlying operating system thread. Through the methods in this class, threads can be started, stopped, interrupted, named, prioritized, and queried regarding their current state.

Note

There are two ways to create a new class that can have a thread running within it. One way is to extend the Thread class. The other is to extend any class and implement the Runnable interface. For the sake of illustration, extending Thread is the simplest approach and is initially used in this book. Implementing the Runnable interface tends to work much better in the real world; this technique is introduced in Chapter 4, "Implementing Runnable Versus Extending Thread."

In this example, the first step towards spawning a new thread is to extend the java.lang.Thread class:

public class TwoThread extends Thread {
    // ...
}

The subclass TwoThread IS-A Thread and consequently inherited the protected and public members from its superclass. TwoThread can be started, stopped, interrupted, named, prioritized, and queried regarding its current state, in addition to all the other behaviors added to the extended class. Figure 2.1 shows the class diagram for TwoThread.

The class diagram for TwoThread.

Figure 2.1. The class diagram for TwoThread.

Note

In this book, the notation used for class relationships is closely based on the Unified Modeling Language (UML). Figure 2.2 shows an example of relationships.

A sample class diagram showing generic relationships.

Figure 2.2. A sample class diagram showing generic relationships.

Different terms are used in this book to describe interclass relationships. All the following phrases are true about the relationships depicted in Figure 2.2:

  • ClassA is the superclass of ClassB.

  • ClassB is a subclass of ClassA.

  • ClassB extends ClassA.

  • ClassB IS-A ClassA.

  • ClassB implements InterfaceF.

  • ClassB IS-A InterfaceF.

  • ClassB is the superclass of ClassC and ClassD.

  • ClassC is a subclass of both ClassB and ClassA.

  • ClassC IS-A ClassB, ClassC IS-A ClassA, and ClassC IS-A InterfaceF.

  • ClassE contains (at least one reference to) ClassB.

  • ClassE HAS-A ClassB reference within it.

Overriding the run() Method

After extending Thread, the next step is to override the run() method because the run() method of Thread does nothing:

public void run() { }

When a new thread is started, the entry point into the program is the run() method. The first statement in run() will be the first statement executed by the new thread. Every statement that the thread will execute is included in the run() method or is in other methods invoked directly or indirectly by run(). The new thread is considered to be alive from just before run() is called until just after run() returns, at which time the thread dies. After a thread has died, it cannot be restarted.

In this chapter's example, run() is overridden with code to loop for 10 iterations and print the message New thread each time through:

public void run() {
    for ( int i = 0; i < 10; i++ ) {
        System.out.println("New thread");
    }
}

After the for loop completes, the thread returns from the run() method and quietly dies.

Spawning a New Thread

New threads are spawned from threads that are already running. First, a new Thread instance must be constructed. In this example, a new TwoThread object will work just fine because TwoThread IS-A Thread:

TwoThread tt = new TwoThread();

The next step is to kick off the execution of the thread by invoking the start() method on the TwoThread object (start() is inherited from Thread):

tt.start();

A call to start() returns right away and does not wait for the other thread to begin execution. In start(), the parent thread asynchronously signals through the JavaVM that the other thread should be started as soon as it's convenient for the thread scheduler. At some unpredictable time in the very near future, the other thread will come alive and invoke the run() method of the Thread object (or in this case, the overridden run() method implemented in TwoThread). Meanwhile, the original thread is free to continue executing the statements that follow the start() call.

The two threads run concurrently and independently. On a multi-processor machine, these two threads may actually be running at the very same instant, each on its own processor. This true simultaneity would also have to be supported in the port of the JavaVM for that platform in order to exploit multiple processors.

A more likely case is that only a single processor is present. The JavaVM and the operating system work together to schedule each thread for short, interleaved bursts of processor usage. Each thread takes a turn running while the other threads are frozen, waiting for their next turn on the processor. This context switching among threads generally occurs very quickly and gives the illusion of truly simultaneous execution.

Caution

A newly created thread may start executing (enter the run() method) at any time after start() is invoked. This means that the original thread might be swapped out before any statement that follows start() is executed.

If the original thread is executing this code

    stmt1();
    tt.start();
    stmt2();

and the new thread has a run() method such as this

  public void run() {
      stmtA();
      stmtB();
  }

the order of actual statement execution in the processor might be stmt1(),tt.start(), stmt2(), stmtA(), and stmtB(). Alternatively, it might be stmt1(), tt.start(), stmtA(), stmtB(), and stmt2(). Perhaps, it might be one of the other permutations!

Important to note is that although the order in which each thread will execute its own statements is known and straightforward, the order in which the statements will actually be run on the processor is indeterminate, and no particular order should be counted on for program correctness.

Putting It All Together

Combining the preceding code and adding a second 10-iteration loop for the main thread to run produces the complete code for TwoThread.java, shown in Listing 2.1.

Example 2.1. TwoThread.java—The Complete Code for the TwoThread Example

 1: public class TwoThread extends Thread {
 2:     public void run() {
 3:         for ( int i = 0; i < 10; i++ ) {
 4:             System.out.println("New thread");
 5:         }
 6:     }
 7:
 8:     public static void main(String[] args) {
 9:         TwoThread tt = new TwoThread();
10:         tt.start();
11:
12:         for ( int i = 0; i < 10; i++ ) {
13:             System.out.println("Main thread");
14:         }
15:     }
16: }

First, note that the new class TwoThread directly extends Thread, so it IS-A Thread and takes on all the capabilities of its superclass.

Application execution begins with the main thread, which is spawned by the JavaVM for all applications at startup, entering the main() method (line 8). It proceeds to create a new TwoThread instance (line 9). Next, it spawns a new thread of execution by invoking the start() method (line 10). This new thread will begin its execution by invoking the run() method (line 2) of TwoThread.

At this point, two threads are ready to run, and the thread scheduler runs each thread for short periods, alternating between them. If this switching back and forth is fast enough, they appear to be running simultaneously.

After the main thread spawns the new thread, the main thread proceeds into its loop (lines 12–14) and prints Main thread to the console 10 times. When it is done with the loop, it falls through and returns from main(). The main thread dies when it returns from main().

At approximately the same time, the new thread enters the run() method (line 2), proceeds into its loop (lines 3–5), and prints New thread to the console 10 times. When it is done with the loop, it falls through and returns from run(). The new thread dies when it returns from run().

When both threads have completed their work and died, the JavaVM shuts down and the application is done.

Listing 2.2 shows possible output from running this application (your output might differ). During this run of the application, the messages from the two threads happened to be perfectly interleaved, starting with the message from the main thread. There is no guarantee that this output would be the same if the application was run again. Thread scheduling is nondeterministic and depends on many factors, including what else is currently running in the operating system. The thread scheduler makes what seem to be random decisions about how long each thread should be allowed to run between context switches. The only thing that can be counted on from running this application is that each message will print exactly 10 times, regardless of the order of the messages.

Example 2.2. Possible Output from a Run of TwoThread.java

Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread
Main thread
New thread

Summary

This chapter explores how to create a new thread by performing these tasks:

  • Subclassing Thread

  • Overriding the run() method of Thread to specify the statements to be executed by the new thread

  • Creating a new instance of this subclass of Thread

  • Spawning a new thread by invoking start() on this instance

That's all there is to getting a second thread up and running in Java! The following chapters explore the complexities of threads and the coordination of the intercommunication that usually must occur among them.

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

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