In a single-threaded program, which is what you've been dealing with up til now, an executing program consists of a function call stack that grows and shrinks as functions are called and functions return. At the bottom of that function call stack is the call record for main(), and when that call returns, the program is over. In a multi-threaded program, there are multiple function call stacks that execute simultaneously. The "main thread" has the main() function call record on the bottom of the stack - other executing function call stacks (an executing function-call stack is called a thread) may have different function calls on the bottom-most function call record. The program ends when every thread has exited, i.e. when the bottom-most function-call record of each call stack has returned.
Ignoring for the moment the very important issues surrounding how the different threads (i.e. the different executing function call stacks) communicate with, and otherwise interact with one another, the fundamental problems that have to be addressed when designing a threading API are:
You might be surprised to find that Java's answer to this involves ... classes, inheritance
and polymorphism. Are you surprised? I hope not! The Java API has a class called
Thread
that includes two important methods:
public void run();
public void start();
Creating a new thread (i.e. a new function call stack)
works like this:
Thread
is created
start()
method is called on the instance
run()
method is called by start(), and that call becomes the bottom-most
record on a new function call stack
import java.util.*;
public class Ex0 {
public static void main(String[] args) {
Thread t = new Foo();
t.start();
int x = 5 * 5;
System.out.println(x);
}
}
public class Foo extends Thread {
public void run() {
int x = 7 * 7;
System.out.println(x);
}
}
Some important points to take away are:
If you look at the above picture, what should strike you the Thread object - more literally the Foo object in this example - is pointed to by references from both threads (from both call stacks). That means that it can be a repository of data that we want to share between threads. We can add fields to Foo for data we want to communicate between the two threads. Below is an example of a fun little program that uses the Thread object to communicate data from the main thread to the new thread. I want you to pay attention to how both threads (both call stacks) make calls to the same methods: Ex1.printSlow().
import java.util.*;
public class Ex1 {
public static Random rand = new Random();
public static void printSlow(String s, String t) {
for (int i = 0; i < s.length(); i++) {
try {
Thread.sleep(rand.nextInt(1000));
} catch (Exception e) {}
System.out.println(t + s.charAt(i));
}
}
public static void main(String[] args) {
String s = "Mississippi";
Thread t = new Foo(s, " ");
t.start();
printSlow(s, " ");
}
}
public class Foo extends Thread {
private String msg, tab;
public Foo(String s, String t) {
this.msg = s;
this.tab = t;
}
public void run() {
Ex1.printSlow(this.msg, this.tab);
}
}