2.3 Writing Threads in Java

 < Day Day Up > 



2.3 Writing Threads in Java

This section demonstrates how to write threads in Java and how the execution of threads differs from the traditional procedural programs with which most programmers are familiar. First, examples of simple procedural and concurrent programs illustrate how they differ in structure. Additional procedural and concurrent programs are then contrasted to show how a concurrent program can produce different, and nondeterministic, results.

2.3.1 Simple Procedural and Concurrent Programs

To demonstrate how to structure a program that uses threads, this section contrasts a simple procedural program with a single method call with a similar concurrent program that starts one thread. The program in Exhibit 1 (Program2.1) is a simple Java procedural program that starts executing in main. This program creates an object of type ProceduralExample and then calls the run method in that object. The main method then suspends executing, and the program continues executing in the run method. When the run method completes, the program returns to the main method, finishes executing in the main method, and then exits the program.

Exhibit 1: Program2.1: Procedural Program

start example

 public class ProceduralExample {   public void run() {     System.out.println("In Run");   }   public static void main(String args[]) {     ProceduralExample pe = new ProceduralExample();     pe.run();   } } 

end example

A similar program using threads is provided in Exhibit 2 (Program2.2). This program also starts in main and creates an instance of the RunnableExample object. The program does not then call the run method in the RunnableExample object, but instead creates a Thread object that is passed to the RunnableExample object as a parameter to its constructor. This "registers" the RunnableExample object with the thread.

Exhibit 2: Program2.2: Concurrent Program Created Using the Runnable Interface

start example

 public class RunnableExample implements Runnable {   public void run() {     System.out.println("In Run");   }   public static void main(String args[]) {     RunnableExample re = new RunnableExample();     Thread t1 = new Thread(re);     t1.start();   } } 

end example

The start method is now called on the Thread object, and the thread begins executing. The main is now free to continue executing (as will be seen in Section 2.3.3), but so is the new thread object. When the start method of the Thread object is called, it starts by calling the run method in the object that was registered with its constructor (in this case, the RunnableExample object). The run method of this object prints out the message and ends. It does not return to the main; instead, because both the main and the created thread have completed executing, the program exits.

Note that the RunnableExample object needed to be declared as "implementing Runnable." This is because the Thread object is expecting to be able to call the run method on this object when its start method is called. Because the Thread object does not know about this RunnableExample object, the only way it can guarantee that the run method will be available is to require that any object that is to be passed to its constructor must implement Runnable, which in turn ensures that it will have a run method to call. This was not necessary in the ProceduralExample program because the definition of the actual object to be called (the ProceduralExample class) was available when the call to the run method was made. Interfaces are covered in greater detail in Chapter 5; for now, simply realize that when thread objects are created they must be given an object that implements Runnable in their definition.

2.3.2 Extending Class Thread

Another way to create a thread does not involve passing an object that implements Runnable to the thread. This can be done by having the class extend the Thread class, as in Program2.3 (Exhibit 3). Because Thread already implements Runnable, the object that extends the Thread class automatically implements the Runnable object. The constructor now does not have an object passed to it because it is itself the Runnable object. The thread can determine if an object was passed to the constructor or not. If one was, it will call the run method on the passed object; otherwise, it will call the run method on the "this," or current, object.

Exhibit 3: Program2.3: Concurrent Program Created by Extending the Thread Class

start example

 public class ThreadExample extends Thread {   public void run() {     System.out.println("In Run");   }   public static void main(String args[]) {     ThreadExample t1 = new ThreadExample();     t1.start();   } } 

end example

These two mechanisms are very similar and can almost be used interchangeably; however, Java supports only a single inheritance model, used by the mechanism to create a thread that extends class Thread. Therefore, classes that extend Thread cannot be used in other inheritance hierarchies, which is a drawback. Because no corresponding negative behavior in regard to using objects that implement Runnable exists, this method is always applicable and preferred. This book always implement threads using the Runnable interface method.

2.3.3 Programs with Multiple Threads

Section 2.3.1 used various mechanisms to describe how the procedural and concurrent programs run; however, because they produced the same results, this is likely to be seen as a distinction without a difference. This section shows how different mechanisms can actually produce very different results.

Program2.4 (Exhibit 4) is a simple procedural program that creates two objects, giving them different numbers so that the outputs from the objects can be distinguished. The run method of the first object is called, producing two lines of output. The program then returns to main, and the run method of the second object is called, again producing two lines of output. This second method completes, and control is returned to main where two more lines are printed out. This output, shown in Exhibit 5, will be the same no matter how many times this program is run. The order of execution is always the same, so we call this execution totally ordered.

Exhibit 4: Program2.4: Procedural Program with Two Method Calls

start example

 public class Procedural {   private int myNum;   public Procedural(int myNum) {     this.myNum = myNum;   }   public static void main(String argv[]) {     Procedural a = new Procedural(1);     Procedural b = new Procedural(2);     a.run();     b.run();     try {       Thread.sleep((int)(Math.random() * 100));       System.out.println("in main");       Thread.sleep((int)(Math.random() * 100));       System.out.println("in main");     } catch(InterruptedException e) {     }   }   public void run() {     try {       Thread.sleep((int)(Math.random() * 100));       System.out.println("in run, myNum = "+ myNum);       Thread.sleep((int)(Math.random() * 100));       System.out.println("in run, myNum = "+ myNum);     } catch(InterruptedException e) {     }   } } 

end example

Exhibit 5: Output from Exhibit 4 (Program2.4)

start example

    in run, myNum = 1    in run, myNum = 1    in run, myNum = 2    in run, myNum = 2    in main    in main 

end example

Program2.5 (Exhibit 6) is similar to Program2.4 (Exhibit 4) except that now the calls to the methods have been replaced with threads. While Program2.5 looks similar to Program2.4, it behaves in a very different way. In Program2.5, the main creates two objects and uses these objects to create two threads, t1 and t2. The start method for thread t1 is called, and now both the main and thread t1 are free to execute. If the main executes first, it starts t2 and all three threads are free to run. Each thread now has an equal chance of running. Thus, the main could run first, followed by t2 twice, then main, and then t1 twice, as in the first example of output from this program provided in Exhibit 7. It is equally likely that t2 will run, followed by main, then t1, t2, main, and t1, as in the second example output shown in Exhibit 7. In fact, it is not very difficult to show that this simple program has 24 combinations of possible outputs.

Exhibit 6: Program2.5: Concurrent Program with Two Threads and a Main Thread

start example

   public Concurrent(int myNum) {     this.myNum = myNum;   }   public static void main(String argv[]) {     Concurrent a = new Concurrent(1);     Concurrent b = new Concurrent(2);     Thread t1 = new Thread(a);     Thread t2 = new Thread(b);     t1.start();     t2.start();     try {       Thread.sleep((int)(Math.random() * 100));       System.out.println("in main");       Thread.sleep((int)(Math.random() * 100));       System.out.println("in main");     } catch(InterruptedException e) {     }   }   public void run() {     try {       Thread.sleep((int)(Math.random() * 100));       System.out.println("in run, myNum = "+ myNum);       Thread.sleep((int)(Math.random() * 100));       System.out.println("in run, myNum = "+ myNum);     } catch(InterruptedException e) {     }   } } 

end example

Exhibit 7: Two Possible Outputs from Exhibit 6 (Program2.5)

start example

 One Possible Output    in main    in run, myNum = 2    in run, myNum = 2    in main    in run, myNum = 1    in run, myNum = 1 Another Possible Output    in run, myNum = 2    in main    in run, myNum = 1    in run, myNum = 2    in main    in run, myNum = 1 

end example

Earlier, a procedural program was said to have a total ordering; that is, it had only one correct ordering of statements in the program. A concurrent program is a partial ordering. By this we mean that even though the threads themselves are ordered and the statements in each thread are actually procedural, the order in which the JVM chooses a thread for execution of its next instruction is not ordered. This allows the statements in the threads to be interleaved, as shown in Exhibit 7. From this example, it is not difficult to see that the number of correct total orderings consistent with the partial orderings imposed by the threads can be very large even for simple concurrent programs. For this program, the number of possible orderings of the output is actually (n + 2)!, where n is the number of threads we start. This large number of correct total orderings makes reasoning about concurrent programs quite complex, much more difficult than for procedural programs. Much of the rest of the text is dedicated to techniques for managing this complexity. How the JVM actually is able to produce this large number of partial orderings is the subject of the Section 2.4, which explains how the JVM builds a process and a thread and how it then uses the information to execute a program.



 < Day Day Up > 



Creating Components. Object Oriented, Concurrent, and Distributed Computing in Java
The .NET Developers Guide to Directory Services Programming
ISBN: 849314992
EAN: 2147483647
Year: 2003
Pages: 162

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net