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
wait()
,notify()
, andnotifyAll()
- How a thread notifies others using
join()
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 java.lang.Thread
,
and override the Thread#run()
method.
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 run()
. 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
extending class Thread
, you can implement the java.lang.Runnable
interface,
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 Thread
:
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
a 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 WhiteWalker
is
RUNNABLE before the waiting the king.
When thread WhiteWalker
calls king.wait(1000)
, it is placed in the waiting
set of threads for the object king
, gives up king
‘s monitor lock, and waits
utils:
- Another thread invokes
notify()
ornotifyAll()
on the same objectking
. - 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 WhiteWalker
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:
2.2. King
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:
In method 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
monitor:
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
behaviors.
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 wait()
, isAlive()
, and notifyAll()
.
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!