Multithread Programming
Unlike some computer languages, Java provides built-in support for
multithreaded programming. A multithreaded program contains two or
more parts that can run concurrently. Each part of such a program is
called a thread, and each thread defines a separate path of execution.
Thus, multithreading is a specialized form of multitasking.
Multitasking is almost supported by all operating systems virtually.
However, there are two types of multitasking:
1. Process Based Multitasking
2. Thread Based Multitasking
A process is, an essence, a program that is executing. Thus, process-
based multitasking is the feature that allows your computer to run two or
more programs concurrently.
In thread-based multitasking, the thread is the smallest unit of the code.
This means that a single program can perform two or more tasks
simultaneously.
Thus, process-based multitasking deals with the “big picture” and
thread-based multitasking handles the details.
Multithreading enables you to write efficient programs that make
maximum use of the processing power available in the system. One
important way, multithreading achieves this by keeping idle time to a
minimum.
Java Thread Model
The Java run-time system depends on threads for many things, and all
the class libraries are designed with multithreading in mind. In fact, Java
uses threads to enable the entire environment to be asynchronous. This
helps reduce inefficiency by preventing the waste of CPU cycles.
Java heavily relies on multithreading to make its runtime system efficient
and asynchronous, preventing CPU waste. Unlike single-threaded
systems, which use event loops and polling that block the entire program
when one task is waiting, Java’s multithreading allows multiple tasks to
run concurrently. If one thread is blocked (e.g., waiting for input or data),
other threads continue running, improving responsiveness and
efficiency.
On single-core systems, threads share CPU time slices, ensuring idle
time is utilized, while on multi-core systems, threads can truly run in
parallel, further boosting performance.
Thread
To understand multithreading, the concepts process and thread must be
understood. A process is a program in execution. A process may be
divided into a number of independent units known as threads. A thread is
a dispatchable unit of work. Threads are light-weight processes within a
process. A process is a collection of one or more threads and associated
system resources. A process may have a number of threads in it. A
thread may be assumed as a subset of a process.
A Process containing single and multiple threads
If two applications are run on a computer (MS Word, MS Access), two
processes are created. Multitasking of two or more processes is known
as process-based multitasking. Multitasking of two or more threads is
known as thread-based multitasking. The concept of multithreading in a
programming language refers to thread-based multitasking. Process-
based multitasking is totally controlled by the operating system. But
thread-based multitasking can be controlled by the programmer to some
extent in a program.
The concept of context switching is integral to threading. A hardware
timer is used by the processor to determine the end of the time-slice for
each thread. The timer signals at the end of the time-slice and in turn the
processor saves all information required for the current thread onto a
stack. Then the processor moves this information from the stack into a
predefined data structure called a context structure. When the
processor wants to switch back to a previously executing thread, it
transfers all the information from the context structure associated with
the thread to the stack. This entire procedure is known as context
switching. Java supports thread-based multitasking.
The advantages of thread-based multitasking as compared to process-
based multitasking are given below:
Threads share the same address space.
Context-switching between threads is normally inexpensive.
Communication between threads is normally inexpensive.
Creating a Thread
Threads are objects in the Java language. They can be created by using
two different mechanisms:
1. Create a class that extends the standard Thread class.
2. Create a class that implements the standard Runnable interface.
That is, a thread can be defined by extending the [Link] class
or by implementing the [Link] interface. The run() method
should be overridden and should contain the code that will be executed
by the new thread. This method must be public with a void return type
and should not take any arguments.
Both threads and processes are abstractions or parallelizing an
application. However, processes are independent execution units that
contain their own state information, use their own address spaces, and
only interact with each other via inter-process communication
mechanisms (generally managed by the operating system).
Creating a Thread by Extending Thread
Class
The steps for creating a thread by using the first meachnaism are:
1. Creating a class by extending the Thread class and override the run()
method:
class MyThread extends Thread{
public void run(){
// thread body of execution
}
}
2. Create a thread object:
MyThread t1 = new MyThread();
3. Start Execution of created thread:
[Link]();
An example program illustrating creation and invocation of a thread
object is given below:
1 class ExtendendingThread extends Thread{
2 public void run(){
3 // thread body of execution
4 [Link]("Child Thread");
5 [Link]("Extended Thread");
6 }
7 }
8 public class ThreadDemo{
9 public static void main(String[] args) {
10 ExtendendingThread t1 = new ExtendendingThread();
11 [Link]();
12 [Link]("Main Thread");
13 }
14 }
Output:
The class ExtendingThread extends the standard Thread class to gain
thread properties through inheritance. The user needs to implement
their logic associated with the thread in the run() method, which is the
body of thread.
The objects created by instantiating the class ExtendingThread are
called threaded objects. Even though the execution method of thread is
called run, we do not need to explicitly invoke this method directly. When
the start() method of a threaded object is invoked, it sets the
concurrent execution of the object from that point onward along with the
execution of its parent thread/method.
Create Thread by Implementing
Runnable Interface
The steps for creating a thread by using the second mechanism are:
1. Create a class that implements the interface Runnable and override
run() method:
public class MyRunnableThread implements Runnable {
public void run() {
// thread body of execution
}
}
2. Creating Object:
MyRunnableThread t1 = new MyRunnableThread();
3. Creating Thread Object:
Thread thread1 = new Thread(t1);
4. Start Execution
[Link]();
An example program illustrating creation and invocation of a thread
object is given below:
1 public class MyRunnableThread implements Runnable {
2 public void run() {
3 for(int i=1; i<=5; i++) {
4 [Link]("Child Thread: " + i);
5 }
6 }
7
8 public static void main(String[] args) {
9 MyRunnableThread t1 = new MyRunnableThread();
10 Thread thread1 = new Thread(t1);
11 [Link]();
12
13 // Main thread work
14 for(int i=1; i<=5; i++) {
15 [Link]("Main Thread: " + i);
16 }
17 }
18 }
Output:
1
2
The class MyRunnableThread implements standard Runnable interface
and overrides the run() method and includes logic associated with the
body of the thread (step 1). The objects created by instantiating the class
MyRunnableThread are normal objects (unlike the first mechanism) (step
2).
Therefore, we need to create a generic Thread object and pass
MyRunnableThread object as a parameter to this generic object (step 3).
As a result of this association, threaded object is created. In order to
execute this threaded object, we need to invoke its start() method which
sets execution of the new thread (step 4).
Thread Class VS Runnable Interface
It is a little confusing why there are two ways of doing the same thing in
the threading API. It is important to understand the implication of using
these two different approaches.
By extending the thread class, the derived class itself is a thread object
and it gains full control over the thread life cycle. Implementing the
Runnable interface does not give developers any control over the thread
itself, as it simply defines the unit of work that will be executed in a
thread.
Another important point is that when extending the Thread class, the
derived class cannot extend any other base classes because Java only
allows single inheritance. By implementing the Runnable interface, the
class can still extend other base classes if necessary.
To summarize, if the program needs a full control over the thread life
cycle, extending the Thread class is a good choice, and if the program
needs more flexibility of extending other base classes, implementing the
Runnable interface would be preferable. If none of these is present,
either of them is fine to use.
Life Cycle of Thread
A thread goes through various stages in its life cycle. For example, a
thread is born, started, runs, and then dies. Following diagram shows
complete life cycle of a thread. It includes:
1. Newborn state
2. Runnable State
3. Running State
4. Blocked State
5. Dead State
Life Cycle of a Thread
1. Newborn State
When we create a thread object, the thread is born and is said to be in
newborn state. The thread is not yet scheduled for running. At this state,
we can do only one of the following with it:
Schedule it for running using start() method
Kill it using stop() method
Scheduling a Newborn Thread
If scheduled, it moves to the runnable state. If we attempt to use any
other method at this stage, an exception will be thrown.
2. Runnable State
The runnable state means that the thread is ready for execution and is
waiting for the availability of the processor. That is, the thread has joined
the queue of threads that are waiting for execution. If all threads have
equal priority, then they are given time slots for execution in round robin
fashion. i.e. first-come, first-serve manner.
The thread that relinquishes control joins the queue at the end and again
waits for its turn. This process of assigning time to threads is known as
time-slicing. If we want a thread to relinquish control to another thread
of equal priority before its turn comes, we can do so by using the yield()
method.
2.
Releasing Control using yield()
3. Running State
Running means that the processor has given its time to the thread for
its execution. The thread runs until it relinquishes control on its own or it
is preempted by a higher priority thread. A running thread may
relinquish its control in one of the following situations:
suspend() and resume() Methods
This approach is useful when we want to suspend a thread for some time
due to certain reason, but do not want to kill it.
sleep() Method
We can put a thread to sleep for a specified time period using the method
sleep (time) where time is in millisecond. This means that the thread is out
of the queue during this time period. The thread re-enters the runnable
state as soon as this time period is elapsed.
wait() and notify() Methods
It has been told to wait until some event occurs. This is done using the
wait() method. The thread can be scheduled to run again the notify()
method.
4. Blocked State
A thread is said to be blocked when it is prevented from entering into the
runnable state and subsequently the running state. This happens when
the thread is suspended, sleeping, or waiting in order to satisfy certain
requirements. A blocked thread is considered “not runnable” but not
dead and therefore fully qualified to run again.
5. Dead State
A running thread ends its life when is has completed executing its run()
method. It is a natural death. However, we can kill it by sending the stop
message to it at any state thus causing a premature death to it. A thread
can be killed as soon it is born, or while it is running, or even when it is in
“not runnable” (blocked) condition.
Thread Priorities
In Java, every thread has a priority or an integer value that tells the
system how important it is compared to other threads.
A higher priority doesn’t make the thread run faster, it simply means
it gets preference when the system decides which thread should
run.
The process of switching the CPU from one thread to another is
called a context switch.