Threads - 2020
In Java, thread means two things:
- An instance of class java.lang.Thread
- A thread of execution
Thread t = new Thread();
t.start();
An instance of Thread is just an object. It has variables and methods like any other objects in Java. It lives and dies on the heap. But a thread of execution is an individual lightweight process. A tread of execution has its own call stack, a separate call stack.
Every Java application starts up a main thread. The main() method, that starts the ball rolling, runs in one thread, call the main thread. The main() is the first method on the stack and it's at the bottom of the main stack. But as soon as we create a new thread, it starts a new stack separate from the main() call stack.
Java runtime environment distinguishes between user thread and daemon threads. As long as a user thread is alive, the JVM does not terminate. A daemon thread is at the mercy of the runtime system: it is stopped if there are no more user threads running, thus terminating the program. Daemon threads exist only to serve user threads.
When a standalone application runs, a user thread is created automatically to execute the main() method of the application. This thread is called the main thread.
If no other user threads are spawned, the program terminates when the main() method finishes. All other threads, called child threads, are spawned from the main thread, inheriting its user thread status. The main method can then finish, but the program will keep running until all the user threads have completed.
Calling the setDaemon(boolean) method in the Thread class marks the status of the thread as either daemon or user. But this must be done before the thread is started, any attempt to change the status after the thread has been started, throws an IllegalThreadStateException. Marking all spawned threads as daemon threads ensures that the application terminates when the main thread dies.
A thread in Java is represented by an object of the Thread class. Implementing threads is done in two ways:
- Implementing the java.Runnable interface.
- Extending the java.lang.Thread class.
But most of the case, we're much more likely to implement Runnable that extend Thread.
The Runnable interface has the following specification, comprising one abstract method declaration:
public interface Runnable { void run(); }
The Runnable interface defines only one method, "public void run()". It's an interface, so the method is public.
Since every thread needs a job. A job the thread will run when the thread is started. That job is actually the first method that goes on the new thread's stack, and it must always be a method that looks like this:
public void run() { // code that will be run by the new thread. }
A thread, which is created based on an object that implements the Runnable interface, will execute the code defined in the public method run(). So, the code inside the run() method defines an independent path of execution. A thread ends when the run() method ends.
Let's summarize how we launch a new thread:
- Make a runnable object (the thread's job)
Runnable threadJob = new MyRunnable();
Runnable is an interface as describe above. We'll write a class that implements the Runnable interface, and that class is where we'll define the work that a thread will perform. In other words, the method will be run from the thread's new call stack. - Make a Thread object (the worker) and give it a Runnable (the job)
Thread myThread = new Thread(threadJob);
Pass the new Runnable object to the Thread constructor. This tells the new Thread object which method to put on the bottom of the new stack, the Runnable's run() method.
How does the thread know which method to put at the bottom of the stack? That's because Runnable defines a contract. Because Runnable is an interface, a thread's job can be defined in any class that implements the Runnable interface. We pass the Thread constructors an object of a class that implements Runnable. - Start the thread
TmyThread.start();
Nothing happens until we call the Thread's start() method. That's when we go from having just a Thread instance to having a new thread of execution. When the new thread starts up, it takes the Runnable object's run() method and puts it on the bottom of the new thread's stack.
Summary: Runnable is to a Thread what a job is to a worker. A runnable is the job a thread is supposed to run. A runnable holds the method that goes on the bottom of the new thread's stack: run().
Here is a sample code:
public class MyRunnable implements Runnable { // (3) public void run() { // (4) doStuff(); } public void doStuff() { System.out.println("doStuff(): Doing lots of things"); } } public class TestingThread { public static void main(String[] args) { Runnable threadJob = new MyRunnable(); // (1) Thread myThread = new Thread (threadJob); // (2) myThread.start(); } }
We split the code into two classes rather than combining both the thread and the job. The Thread class for the thread-specific code and our Runnable implementation class for our job-that-should-be-run-by-a-thread code.
(1) Pass the new Runnable instance into the new Thread constructor. This tells the thread what method to put on the bottom of the new stack. In other words, the first method that the new thread puts will run.
(2) We won't get a new thread of execution until we call start() on the Thread instance. A thread is not really a thread until we start it. Before that, it won't have any real threadness.
(3) Runnable is in the java.lang.package, so we don't need to import it.
(4) Runnable has only one method to implement; "public void run()". This is where we put the job the thread is supposed to run. This is the method that goes at the bottom of the new stack.
Let's take another approach: extending the Thread class.
class MyThread extends Thread { public void run() { System.out.println("Doing lots of things"); }
The simplest way to define code to run in separate thread is to
- Extend the java.lang.Thread class.
- Override the run() method.
The limitation with the approach (note that this approach is a poor design choice in most cases) is that if we extend Thread, we can't extend anything else. And it's not as if we really need that inherited Thread class behavior, because in order to use a thread we'll need to instantiate one anyway.
Short Quiz: What's going to happen when we run the following code.
Thread t = new Thread(); t.run();
There's nothing special about the run() method as far as Java is concerned. Like main(), it just happens to be the name and signature of the method that the new thread knows to invoke.
So if you see code that calls the run() method on a Runnable, that's perfectly legal. But it doesn't mean that run() method will run in a separate thread!
Calling a run() method directly just mean you're invoking a method from whatever thread is currently executing, and the run() method goes onto the current call stack rather than at the beginning of a new call stack. So, the above code does not start a new thread of execution.
Let's look at what's happening at t.start()
Runnable job = new MyRunnable(); // (1) Thread t = new Thread (job); // (2) t.start();
Prior to calling start() on a Thread instance, the thread is said to be in the new state. The new state means we have a Thread object but we don't yet have a true thread. In other words, there is a Thread object, but no thread of execution.
So what happens after we call start()?
- A new thread of execution starts with a new call stack.
- The thread moves from the new to the runnable state. This means the thread is ready to run and just waiting for its chance to be selected for execution. At this point, there is a new call stack for this thread. Once the thread becomes runnable, it can move back and forth between runnable, running, and an additional state: temporarily not runnable(so called blocked state).
- When the thread get a chance to execute, its target run() method will run. In this running state, a thread has an active call stack, and the method on the top of the stack is executing.
We start a Thread, not a Runnable. We call start() on a Thread instance, not on a Runnable instance.
Typically, a thread moves back and forth between runnable and running, as the JVM thread scheduler selects a thread to run and then kicks it back out so another thread gets a chance.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization