The Three States Of A New Thread
NEW
Thread thread = new Thread(runnable);
A Thread instance has been created but has not yet been started. In other words, there is a Thread object but no thread of execution.
RUNNABLE
thread.start();
The thread enters the runnable state when you start it. This indicates that the thread is ready to run and is just waiting for its Big Chance to be chosen for execution. This thread has a new call stack at this point.
RUNNING
This is the state that all threads desire! To be chosen as The Chosen One.
The Currently Active Thread. That decision can only be made by the JVM thread scheduler. You can sometimes sway that decision, but you can never force a thread to go from runnable to running. A thread (and ONLY this thread) has an active call stack in the running state, and the method at the top of the stack is executing.
But wait, there's more. Once a thread becomes runnable, it can switch between runnable, running, and an additional state: temporarily not runnable (also known as 'blocked').
Typical Runnable/Running Loop
Typically, a thread switches between runnable and running states as the JVM thread scheduler selects a thread to run and then kicks it back out to give another thread a chance.
A thread can be made temporarily not-runnable
The thread scheduler can put a running thread into a blocked state, for a number of reasons. For example, a thread may execute code to read from the Socket input stream, but there is no data to read. The Scheduler moves a thread out of the running state until something becomes available. Or the executing code could tell the thread to sleep (sleep()). Or a thread may wait because it tried to call a method on an object and that object was “locked”. In this case, the thread cannot continue until the lock on the object is released by the thread containing it.
All of those conditions (and more) cause a thread to become temporarily not-runnable.
What Is The Thread Scheduler?
The thread scheduler determines who moves from runnable to running and when (and under what conditions) a thread exits the running state. The scheduler decides who runs and for how long, as well as where threads go when the scheduler decides to remove them from the currently running state.
You have no control over the scheduler. There is no API for calling methods on the scheduler. Most importantly, there are no scheduling guarantees! (There are a few near-guarantees, but even those are hazy.)
The bottom line is that you should not base the correctness of your program on the scheduler working in a specific way! Scheduler implementations differ between JVMs, and even running the same program on the same machine can produce different results. One of the most common mistakes new Java programmers make is testing their multi-threaded program on a single machine and assuming that the thread scheduler will always work the same way regardless of where the program runs.
So, what does this mean for the write-once, run-anywhere model?
That is, in order to write platform-independent Java code, your multi-threaded program must function regardless of how the thread scheduler behaves. That means you can’t rely on the scheduler, for example, to ensure that all threads take nice, perfectly fair, and equal turns at the running state. Although this is highly unlikely today, your program could end up running on a JVM with a scheduler that says, “OK thread five, you’re up, and as far as I’m concerned, you can stay here until your run() method completes”.
Sleep is the key to almost everything. That’s correct, sleep. Even putting a thread to sleep for a few milliseconds causes the currently running thread to exit the running state, allowing another thread to run. The sleep() method of the thread does have one guarantee: a sleeping thread will not become the currently-running thread before the length of its sleep time has expired. For example, if you tell your thread to sleep for two seconds (2,000 milliseconds), it will never be the running thread again until after the two seconds have passed.
An example of the scheduler’s unpredictability…
class RunnableTask implements Runnable{
public void run() {
doSomething();
}
public void doSomething() {
doSomethingMore();
}
public void doSomethingMore() {
System.out.println("This method is at the top of the stack!!");
}
}
public class RunnableDemo2 {
public static void main(String[] args) {
Runnable runnableTask = new RunnableTask();
Thread thread1 = new Thread(runnableTask);
thread1.start();
System.out.println("Back In Main!");
}
}
Output
After running this program, take note of how the order changes at random. Sometimes the new thread is completed first, and sometimes the main thread is completed first.
Your output may be:
back in main
top o’ the stack
Or,
top o’ the stack
back in main
How did we arrive at various outcomes?
Q:I’ve seen examples that don’t use a separate Runnable implementation, but instead create a Thread subclass and override the Thread’s run method (). In this manner, you call the Thread’s no-arg constructor when creating a new thread;
Thread t = new Thread(); // there is no Runnable.
Yes, that is another way to make your own thread. We have shown you this approach earlier. But consider it from an OO standpoint. What is the point of subclassing? Remember that we’re discussing two distinct things here: the Thread and the Thread’s Job. From an OO standpoint, those two activities are very distinct and should be classified as such. Only when creating a new and more specific type of Thread should you subclass or extend the Thread class. In other words, if you consider the Thread to be the worker, you should not extend the Thread class unless you require more specific worker behaviors. If all you need is a new job to be executed by a Thread/worker, then implement Runnable in a separate, job-specific (rather than worker-specific) class.
This is a design problem, not a performance or language problem. Subclassing Thread and overriding the run() method is perfectly legal, but it’s rarely a good idea.
Q: Is it possible to reuse a Thread object? Can you assign it a new task and restart it by calling start() again?
No. Once a thread’s run() method has finished, it can never be restarted. In fact, at that point, the thread enters a state we haven’t discussed: dead. The thread has completed its run() method and cannot be restarted in the dead state.
The Thread object may still be on the heap as a living object on which you can call other methods (if appropriate), but it has permanently lost its ‘threadness’. In other words, the Thread object is no longer a thread, and there is no longer a separate call stack. At that point, it’s just an object, like all other objects.
However, there are design patterns for creating a pool of threads that you can use to perform various tasks. However, you do not accomplish this by restarting() a dead thread.
KEY POINTS
In Java, a thread with a lower-case 't' is a separate thread of execution.
In Java, each thread has its own call stack.
A Thread with a capital ‘T’ is the java.lang.Thread class. A Thread object represents a thread of execution.
A Thread requires a task. The job of a Thread is an instance of something that implements the Runnable interface.
The Runnable interface has only one method, run (). This method is added to the bottom of the new call stack. In other words, it is the first method that will be executed in the new thread.
A Runnable must be passed to the Thread's constructor in order to start a new thread.
When you instantiate a Thread object but do not yet call start(), the thread is in the NEW state.
When you start a thread (via the Thread object's start() method), a new stack is created, with the Runnable's run() method at the bottom. The thread is now in the RUNNABLE state, waiting to be selected for execution.
When the JVM's thread scheduler selects a thread to be the currently running thread, it is said to be RUNNING. There can only be one thread running on a single-processor machine.
A thread can be moved from the RUNNING state to the BLOCKED (temporarily non-runnable) state at any time. A thread may be blocked because it is waiting for data from a stream, has gone to sleep, or is waiting for an object's lock.
Because thread scheduling is not guaranteed to work in any particular way, you cannot be certain that threads will take turns in a pleasant manner. You can influence turn-taking by putting your threads to sleep on a regular basis.