You can think of a thread as a virtual processor that has been given the task of executing a particular method. When you run a Java application, the Java Virtual Machine creates a new thread, called the main thread, whose job is to exeute the main() method of that program.
During execution, a Java program can create new threads that, once started, execute concurrently —and possibly in parallel, if there are multiple processors— with the program.
The means by which one sets up a thread are somewhat confusing, at least at first. There are a few ways of doing it; this document focuses on only one of them. For a more comprehensive explanation, the reader is recommended to read Jakob Jenkov's tutorial.
public interface Runnable { public void run(); } |
As a contrived example, consider this class:
public class PrintGrisGrop implements Runnable { // Prints "GrisGrop" ten times, on separate lines. public void run() { for (int i=0; i != 10; i++) { System.out.println("GrisGrop"); } } } |
To create a thread, you invoke the constructor of the class java.lang.Thread, passing to it an instance of a class that implements the Runnable interface. A thread, then, is an object that has what one might refer to as its "resident Runnable object". As a matter of terminology, if thread A creates thread B, we refer to A as being the parent of B (and B as the child of A).
Once created, a thread's execution is started by invoking its start() method, which initiates a call to the run() method of the thread's resident Runnable object. Continuting our example, consider the following application program:
public class GrisGrop1 { public static void main(String[] args) { Runnable grisGropObj = new PrintGrisGrop(); Thread grisGropThread = new Thread(grisGropObj); grisGropThread.start(); } } |
Of course, the effect of running this program will be for the string "GrisGrop" to be printed ten times, as that is what the run() method of grisGropObj does, and that method is called as a result of the call grisGropThread.start().
Consider this augmented version of the program:
public class GrisGrop2 { public static void main(String[] args) { Runnable grisGropObj = new PrintGrisGrop(); Thread grisGropThread = new Thread(grisGropObj); grisGropThread.start(); for (int i = 0; i != 10; i++) { System.out.println("Blorp!"); } } } |
This program's output will span twenty lines, ten of which say GrisGrop and ten of which say Blorp, but the order in which they appear is unpredictable. That's because the child thread and the for-loop that follows the call to start that thread execute concurrently, not sequentially. Of course, this is in marked contrast to the behavior resulting from a call to a "normal" method, where execution of the caller does not continue until after the called method has completed its execution.
Now, suppose that we wanted to guarantee that the ten GrisGrop's will be printed before the ten Blorp's. To achieve that, we could use the child thread's join() method, like this:
public class GrisGrop3 { public static void main(String[] args) throws InterruptedException { Runnable grisGropObj = new PrintGrisGrop(); Thread grisGropThread = new Thread(grisGropObj); grisGropThread.start(); grisGropThread.join(); for (int i = 0; i != 10; i++) { System.out.println("Blorp!"); } } } |
The effect of the join() method is that the caller cannot continue execution until the specified thread has completed execution. For this example, it means that all the GrisGrop's will have been printed (during execution of the child thread) before execution of the loop that prints the Blorp's is allowed to begin.
A call to the join() method could result in an InterruptedException being thrown. That being in the category of checked exceptions (as opposed to unchecked), one must either handle it using a try-catch block or else the calling method must declare that its execution might result in that kind of exception being thrown. In the example, the latter option was chosen.
In order to avoid the clutter caused by either of these options, this author likes to make use of the following home-grown method:
/* Applies join() to the given thread, catching ** InterruptedException if it is thrown. */ private void join(Thread t) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); } } |
Now let's see how we can generalize the PrintGrisGrop class so that it's more versatile, but at the same time it is still possible to use an instance of it as a "resident Runnable" within a thread.
The run() method of PrintGrisGrop prints the string GrisGrop ten times. There are two obvious generalizations to this behavior:
The obvious way to make it possible for a client program to specify the string to be printed, and how many times to print it, is to have the run() method receive parameters that specify that information. But that won't work, because the run() method, to be consistent with the Runnable interface, must be a parameter-less method. Recall that, for an object to play the role of a thread's "resident Runnable", it must be an instance of a class that implements the Runnable interface.
The solution to this obstacle is to make use of instance variables for storing the information necessary for the run() method to carry out its intended task. The values of those instance variables can be established via a call to the class's constructor.
With this in mind, we replace the PrintGrisGrop class with this one:
public class StringPrinter implements Runnable { // instance variables // ------------------ private String str; // the string to be printed private int n; // # of times to print it /* Establish the string to be printed by the run() ** method, and how many times it is to be printed. ** pre: numTimes >= 0 */ public StringPrinter(String s, int numTimes) { this.str = s; this.n = numTimes; } /* Prints the string established at construction the ** number of times established at construction. */ public void run() { for (int i=0; i != n; i++) { System.out.println(str); } } } |
With this more versatile class, we can develop a program that uses two child threads that run concurrently, each printing its own string numerous times:
public class GrisGrop4 { public static void main(String[] args) { // Create two instances of StringPrinter. Runnable grisGrop = new StringPrinter("GrisGrop", 15); Runnable blorp = new StringPrinter("Blorp", 11); // Create two threads, each having an instance // of StringPrinter as its "resident Runnable". Thread grisGropThread = new Thread(grisGrop); Thread blorpThread = new Thread(blorp); // Start both threads grisGropThread.start(); blorpThread.start(); // Wait for both threads to finish. join(grisGropThread); join(blorpThread); // Print a goodbye message. System.out.println("Goodbye."); } /* Applies join() to the given thread, catching ** InterruptedException if it is thrown. */ private static void join(Thread t) { try { t.join(); } catch (InterruptedException e) { e.printStackTrace(System.out); } } } |
This program will print GrisGrop 15 times and Blorp 11 times, but the order in which they appear is not predictable. That is, GrisGrop might appear four times, followed by seven occurrences of Blorp, followed by ...
Will appear at a later date.