Today, I want to share with you an introduction to multithreading in Java. In order to make the blog post more fun, I’ll use the roles of “Game of Thrones” in my examples: white walkers and the Night King. After reading the blog post, you’ll understand:
- How to create and run a thread
- How a thread notifies others using
- How a thread notifies others using
Let’s get started.
The Night King and his army | HBO
1. How to Create and Use Threads
First of all, let’s ensure we know how to create and use threads. There’re several ways to create and start a thread.
1.1. Extending Class Thread
You can create your own Java object by extending the class
and override the
Once everything is done, you must start the thread by calling method
Thread#start(). When a new thread of execution stats, it will execute the code
defined in the thread instance’s method
start() will trigger
creation of a new thread of execution, allocating resources to it.
However, when you create a thread class by extending class
Thread, you lose
the flexibility of inheriting any other class. To get around this, instead of
Thread, you can implement the
which will be discussed later.
1.2. Using Anonymous Class
If you want to create a
Thread without being bothered by the inheritance, you
can use the anonymous class, which overrides the
Thread#run() method in-place:
1.3. Implementing Interface Runnable
If a class implements
java.lang.Runnable, its instances can be executed by
threads of execution via method
#run(). For example:
This class can be executed by
1.4. Using Lambda
In Java 8, you can forget all the ceremonies and use lambda expression :)
Now I would like to explain how threads wait, and notify others using different approaches: wait-notify, or join. There’re many other models, but only these two will be discussed in the blog post. These actions are related to thread lifecycle.
Let’s ues white walkers as our context. Assume that there’s a Night King and many white walkers. The Night King and each white walker uses its own thread, but the white walkers cannot move before the Night King is ready. Once ready, the white walkers can prepare and follow the king after that. How can we implement this scene?
2. Wait, Notify and NotifyAll
The Night King notifies his army. | HBO
2.1. White Walker
Firstly, we need to write a class for the army—white walkers. White walker
extends the class
Thread, so that they can be started easily. A Night King
instance is assigned to the white walker, so the white walker can understand
who to follow:
Now, let’s take a look in the method
run(). Inside the
run() method, white
walker waits until the
king is ready. When digging deeper, you can notice that
synchronized statement is used before calling method
wait(). This is
because a thread must acquire a lock on an object monitor, before it can
execute the synchronized statements. The state of thread
RUNNABLE before the waiting the king.
king.wait(1000), it is placed in the waiting
set of threads for the object
king, gives up
king ‘s monitor lock, and waits
- Another thread invokes
notifyAll()on the same object
- Some other threads interrupt
WhiteWalker, e.g. Jon Snow :)
- The timeout 1000 ms is reached, in which case the white walker will die.
Immediately after calling
Thread#wait(long), the current thread
changes its thread state, from RUNNABLE to TIMED_WAITING. TIMED_WAITING
means a thread is waiting for another thread to perform an action for up to a
specified time is in this state.
Here’s the implementation:
Let’s take a look on class
NightKing. Night king will be ready 100 ms after
having started its preparation. Once ready, he will notify all the white
walkers, the entire army that is waiting for him:
ready(), king uses method
Thread#notifyAll() to notify his army.
It means that king will wake up all threads that are waiting on this object’s
Notice that in the method above, the keyword
synchronized is necessary to
acquire a monitor of the Night King instance. The threads waiting on this
object’s monitor, they will compete in the usual manner to acquire a lock on the
object’s monitor and resume their execution.
2.3 Source code
3. Method join()
A thread might need to pause its own execution when it is waiting for another
thread to complete its task. If thread A calls
join() on a thread instance B,
A will wait for B to complete its execution before A can proceed to its own
completion. Now let’s see how “join” can change the white walker army’s
3.1 White Walker
Comparing to the previous approach in section 2, where we need to acquire a monitor of the Night King, now using “join”, we can simply describe the workflow as: “Any white walker following the Night King will join him when he’s ready.”
When a white walker calls
king.join(), the thread of white walker waits for
the king to complete its execution before executing the rest of its code. Method
join() guarantees that the calling thread won’t execute its remaining code
until the thread on which it calls
join() completes. Behind the scenes,
join() is implemented using methods
3.2 Night King
This approach also simplifies the logic for the Night King, who does not need to notify all the threads anymore:
However, we must understand that this approach is not the same as the notify-all
approach. Using method
join() requires the execution of target thread (king)
to be complete, before the army can resume their execution. While using
notify-all, you can let king to notify all in middle of his execution, then
resume after that.
3.3 Source Code
I’m not 100% about these two approaches. If you found some wrong in this blog post, please don’t hesitate to leave me a comment. Hope you enjoy this one, see you the next time!