What are "threads"?
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:
-
How do you start a new thread? I.e. how do you call a
function so that instead of the call resulting in a new
function call record being pushed on top of the stack
record of the caller in the caller's function call stack,
a new stack is created and the function call becomes the
bottom-most record on the new stack?
-
How do you get data into the newly created function call
stack when you start it?
-
How do you get data out of the new function call stack
once it's finished executing?
Java threads: the very basic basics
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:
- an instance of
Thread is created
- the
start() method is called on the instance
-
the
run() method is called by start(), and that
call becomes
the bottom-most record on a new function call stack
So, if you have something specific you want a new thread to do,
you derive a new class from Thread (extend Thread) and override
the run() method to do whatever it is you want done.
Hopefully the following picture gives you some idea of how
this works.
Some important points to take away are:
-
It's wrong to say "such and such object is in thread 1".
Objects live in the heap, outside of any of the call-stacks!
-
Local varaiables, be they primitive types or references,
do belong to a particular thread, since they live in
function call records.
-
It's wrong to say "such and such function is in thread 1".
A given function call belongs to a thread, but
there could be many threads with active function calls for
the same function.
Getting data in and out: it's all in the Object
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().
Not pictured here is the Random object.