Threads II : State Transition - 2020
Before we start multiple threads, we need to know how to print out which thread is executing.
We use the getName() method of Thread class, and make each Runnable print out the name of the thread execution that Runnable object's run() method.
class MyRunnable implements Runnable { public void run() { System.out.println("MyRunnable is running by " + Thread.currentThread().getName()); } } public class TestingThread { public static void main(String[] args) { Runnable job = new MyRunnable(); Thread t = new Thread (job); t.start(); } }
If we run it, we get the following information from the output.
MyRunnable is running by Thread-0
To get the name of a thread, we call getName() on the Thread instance. But the target Runnable instance doesn't even have as reference to the Thread instance. So we first invoked the static Thread.currentThread() method, which returns a reference to the currently executing thread. Then we invoked getName() on that returned reference.
To use our name for the thread, we can set it as follows.
public class TestingThread { public static void main(String[] args) { Runnable job = new MyRunnable(); Thread t = new Thread (job); t.setName("Beethoven."); t.start(); } }
The new output we get:
MyRunnable is running by Beethoven.
In the example above, we're getting the name of the current thread using the static method, Thread.currentThread(), we can even get the name of the thread running our main code:
public class TestingThread { public static void main(String[] args) { System.out.println("This thread is " + Thread.currentThread().getName() + " Thread!"); } }
Output is:
This thread is main Thread!
Surprisingly, the main thread name is main.
Let's run the following code.
class MyRunnable implements Runnable { public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread a = new Thread(r); Thread b = new Thread(r); a.setName("a thread"); b.setName("b thread"); a.start(); b.start(); } public void run() { for (int i = 0; i < 20; i++) { System.out.println("Running " + Thread.currentThread().getName()); } } } } }
Will the threads take turns?
Will we see the thread names alternating?
Will there be a pattern?
We do not know.
1: Running a thread 2: Running a thread 3: Running a thread 4: Running a thread 5: Running a thread 6: Running a thread 7: Running a thread 8: Running a thread 9: Running a thread 10: Running a thread 1: Running b thread 2: Running b thread 3: Running b thread 4: Running b thread 5: Running b thread 6: Running b thread 7: Running b thread 8: Running b thread 9: Running b thread 10: Running b thread
Looks fine and everything in order. b runnable was patiently waiting for a to finish running. But when we increase the loop limit to 20,
for (int i = 1; i <= 20; i++) { System.out.println(i + ": Running " + Thread.currentThread().getName()); }
things start to wobble.
1: Running a thread 2: Running a thread 3: Running a thread 1: Running b thread 2: Running b thread 3: Running b thread 4: Running b thread 5: Running b thread 4: Running a thread 5: Running a thread 6: Running a thread 7: Running a thread 8: Running a thread 9: Running a thread 10: Running a thread 11: Running a thread 12: Running a thread 13: Running a thread 14: Running a thread 15: Running a thread 16: Running a thread 17: Running a thread 18: Running a thread 19: Running a thread 20: Running a thread 6: Running b thread 7: Running b thread 8: Running b thread 9: Running b thread 10: Running b thread 11: Running b thread 12: Running b thread 13: Running b thread 14: Running b thread 15: Running b thread 16: Running b thread 17: Running b thread 18: Running b thread 19: Running b thread 20: Running b thread
But the behavior we see above is not guaranteed. We need to know that there is nothing in Java specification that says threads will start running in the order in which they were started. Also, there is no guarantee that once a thread starts execution, it will keep executing until it's done. Or that a loop will complete before another thread begins. Nothing is guaranteed but one thing:
Each thread will start, and each thread will run to completion.
Within each thread, things will happen in a predictable order. But the actions of different threads can mix together in unpredictable ways. We don't know, for example, if one thread will run to completion before the others have a chance to get in or whether they'll all take turns nicely, or whether they'll do a combination of both. There is a way, however, to start a thread but tell it not to run until some other thread has finished. We can do this with the join() method.
When a thread completes its run() method, the thread ceases to be a thread of execution. The stack for that thread dissolves, and the thread is considered dead. But it's a still a Thread object, just not a thread of execution. So, if we've got a reference to a Thread instance, then even when that Thread instance is no longer a thread execution, we can still call methods on the Thread instance. What we can't do is call start() again.
Once a thread has been started, it can never be started again.
If we have a reference to a Thread, and we call start(), it's started. If we call start() the second time, it will cause an exception (in IllegalThreadStateException, which is a kind of RuntimeException.) This happens whether or not the run() method has completed from the first start() call. Only a new thread can be started, and then only once. A runnable thread or a dead thread cannot be restarted. Thread can't recover from the dead state.
So, a Thread object is not reusable. Once a thread's run() method has completed, the thread can never be restarted. Dead, Dead, Dead! The Thread object might still be on the heap, as a living object that we can call other methods on, but the Thread object has permanently lost its threadness. In other words, there is no longer a separate call stack, and the Thread object is no longer a thread. It's just an object.
But, there are design patterns for making a pool of threads that we can keep using to perform different jobs. But we don't do it by restarting() a dead thread.
The thread scheduler in JVM makes all the decision about who moves from runnable to running, and about when and under what circumstances a thread leaves the running state. The scheduler not only decides who runs but also for how long, and where the threads go when the scheduler decides to kick them out of the currently-running state.
Any thread in the runnable state can be chosen by the scheduler to be the one and only running thread. If a thread not in a runnable state, then it cannot be chosen to be the currently running thread. Although queue behavior is typical, it is not guaranteed.
We can't control the scheduler. So, we should not base our program's correctness on the scheduler working in a particular way. The scheduler implementations are different for different JVM's, and even running the same program on the same machine can give us different results.
Although we don't control the thread scheduler, we can sometimes influence it. The following methods give us some tools for influencing the scheduler.
- Methods from the java.lang.Thread Class
public static void sleep(long millis) throws InterruptedException public static void yield() public final void join() throws InterruptedException public final void setPriority(int newPriority)
- Methods from the java.lang.Object Class
public final void wait() throws InterruptedException public final void notify() public final void notifyAll()
So does it mean when we say write-once-run-anywhere?
It means that to write platform-independent Java code, our multi-threaded program must work no matter how the thread scheduler behaves.
The secret to almost everything is sleep. Putting a thread to sleep, even for a few milliseconds, forces the currently-running thread to leave the running state, thus giving another thread a chance to run. The thread's sleep() method does come with oneguarantee: a sleeping thread will not become the currently-running thread before the length of its sleep time has expired.
Understanding the life cycle of a thread is critical when programming with threads. Threads can exist in different states. Just because a thread's start() method has been called, it does not necessarily mean that the thread has access to the CPU and can start executing right away. Several factors determine how it will proceed.
A thread can be in one of five states during its life cycle.
- New
This is the state the thread is in after the Thread instance has been created, but start() method has not been invoked on the thread. It's a live Thread object, but not yet a thread of execution and considered not alive. - Runnable
This is the state a thread is in when it's ready to run and just waiting for its chance to be selected for execution. But the scheduler has not selected it to be the running thread. A thread first enters the runnable state when the start() method is invoked. But a thread can also return to the runnable state after either running or coming back from a blocked, waiting, or sleeping state. When the thread is in the runnable state, it is considered alive. - Running
This is the state all threads lust after! To be the chosen one. This is the state where the action is. This is the state a thread is when the thread scheduler selects it from the runnable pool to be the currently executing process. There are several ways to get to the runnable state, but only one way to get to the running state: the scheduler selects a thread from the runnable pool. - Waiting/blocked/sleeping
This is the state a thread is in when it's not eligible to run. This is the state three in one. But they all share common thing: the thread is still alive, but is not eligible to run.
It's not in a runnable state. But it might return to a runnable state later. A thread may be blocked waiting for a resource such as object's lock. In this case, the event that sends it back to runnable is the availability of the resource such as the object's lock suddenly become available.
A thread may be sleeping because the thread's run code tells it to sleep. In this case, the thread can be send back to runnable if it wakes up.
The thread may be waiting, because the thread's run code causes it to wait. In this case, the event that sends it back to runnable state is a notification from another thread saying it may no longer be necessary for the thread to wait.
One thing keep in mind is that one thread does not tell another thread to block. Some methods may look like they tell another thread to block, but they don't. If we have a reference t to another thread, we can write something like this:
t.sleep();
ort.yield();
But those are actually static method of the Thread class. So, they do not affect the instance. There are stop() and suspend() methods that let one thread tell another thread but they are all deprecated. - Dead
Once is this state, the thread cannot ever run again. A thread is considered dead when its run() method completes. It may still be a viable Thread object, but it is no longer a separate thread of execution. If we invoke start() on a dead Thread instance, we'll get runtime exception.
One of the best ways to help our threads take turns is to put them to sleep periodically.
Let's look at the following code:
class MyRunnable implements Runnable { public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread a = new Thread(r); Thread b = new Thread(r); Thread c = new Thread(r); a.setName("thread a "); b.setName("thread b "); c.setName("thread c "); a.start(); b.start(); c.start(); } public void run() { for (int i = 1; i <= 5; i++) { System.out.println( Thread.currentThread().getName() + i + " is running"); } } }
Output is:
thread a 1 is running thread a 2 is running thread c 1 is running thread c 2 is running thread c 3 is running thread c 4 is running thread c 5 is running thread a 3 is running thread b 1 is running thread b 2 is running thread b 3 is running thread b 4 is running thread b 5 is running thread a 4 is running thread a 5 is running
As we see from the output, we do not know which thread will be the next running thread. However, we things will be less unpredictable if we use sleep.
We use it in our code to go to sleep mode by force before coming back to runnable. When a thread sleeps, it drifts off somewhere and doesn't return to runnable until it wakes up.
All we need to do is call the static sleep() method. For example,
Thread.sleep(2000);will knock a thread out of the running state, and will keep out of the running state for two seconds. The thread can't become the running thread again until after at least two seconds have passed.
But the sleep method throws an InterruptedException, a checked exception, so all calls to sleep must be wrapped in a try/catch.
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
If we put the sleep() into the code above:
class MyRunnable implements Runnable { public static void main(String[] args) { MyRunnable r = new MyRunnable(); Thread a = new Thread(r); Thread b = new Thread(r); Thread c = new Thread(r); a.setName("thread a "); b.setName("thread b "); c.setName("thread c "); a.start(); b.start(); c.start(); } public void run() { for (int i = 1; i <= 5; i++) { System.out.println( Thread.currentThread().getName() + i + " is running"); try { System.out.println( Thread.currentThread().getName() + i + " is going to sleep"); Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Output is:
thread a 1 is running thread c 1 is running thread b 1 is running thread c 2 is running thread a 2 is running thread b 2 is running thread a 3 is running thread c 3 is running thread b 3 is running thread c 4 is running thread a 4 is running thread b 4 is running thread c 5 is running thread a 5 is running thread b 5 is running
As we see, the outcome is less unpredictable. Clearly, the threads a, b, and c take turns though the order of running keeps changing.
Just because a thread's sleep() expires, and it wakes up, does not guarantee it will return to running state. When a thread wakes up, it simply goes back to the runnable state. So the time specified in sleep() is the minimum duration in which the thread guaranteed not to run, but it is not the exact duration in which the thread won't run. So, we can't rely on using sleep() as a timer because a sleep() time is not a guarantee that the thread will start running again as soon as the time expires and the thread wakes.
Ph.D. / Golden Gate Ave, San Francisco / Seoul National Univ / Carnegie Mellon / UC Berkeley / DevOps / Deep Learning / Visualization