MULTITHREADING
Multithreading is a programming paradigm that allows multiple threads to run concurrently
within a single program. A thread is essentially a lightweight process that can execute tasks
independently, sharing the same memory space with other threads. Multithreading is a Java
feature that allows concurrent execution of two or more parts of a program for maximum
utilization of CPU. Each part of such program is called a thread. So, threads are light-weight
processes within a process.
The primary goals of multithreading include:
Improved Performance: By executing multiple threads simultaneously, applications can
utilize CPU resources more effectively, leading to faster execution times.
Responsiveness: Multithreading allows applications to remain responsive, especially in
user interfaces where tasks like loading data can be done in the background.
Resource Utilization: It enables better utilization of CPU resources by overlapping I/O
operations with processing tasks
Threads in Java
In Java, a thread is represented by the Thread class, which is part of the java.lang package.
Java provides two main approaches to create threads:
1. Extending the Thread Class: By creating a subclass of the Thread class and overriding
its run() method.
2. Implementing the Runnable Interface: By implementing the Runnable interface, which
requires defining the run() method.
Example of Extending the Thread Class:
class MyThread extends Thread {
public void run() {
// Task to be performed
System.out.println("Thread is running.");
}
Example of Implementing Runnable Interface:
class MyRunnable implements Runnable {
public void run() {
// Task to be performed
System.out.println("Runnable thread is running.");
// Java code for thread creation by extending the Thread class
class MultithreadingDemo extends Thread {
public void run()
try {
// Displaying the thread that is running
System.out.println(
"Thread " + Thread.currentThread().getId()
+ " is running");
catch (Exception e) {
// Throwing an exception
System.out.println("Exception is caught");
}
}
// Main Class
public class Multithread {
public static void main(String[] args)
int n = 8; // Number of threads
for (int i = 0; i < n; i++) {
MultithreadingDemo object
= new MultithreadingDemo();
object.start();
Thread Class vs Runnable Interface
1. If we extend the Thread class, our class cannot extend any other class
because Java doesn’t support multiple inheritance. But, if we implement
the Runnable interface, our class can still extend other base classes.
2. We can achieve basic functionality of a thread by extending Thread class
because it provides some inbuilt methods like yield(), interrupt() etc. that
are not available in Runnable interface.
3. Using runnable will give you an object that can be shared amongst
multiple threads.
Lifecycle of a Thread
A thread in Java goes through several states during its lifecycle:
1. New: The thread is created but not yet started. When a new thread is created, it is in the
new state. The thread has not yet started to run when the thread is in this state. When a
thread lies in the new state, its code is yet to be run and hasn’t started to execute.
2. Runnable: The thread is ready to run and may be running or waiting for CPU time. A
thread that is ready to run is moved to a runnable state. In this state, a thread might
actually be running or it might be ready to run at any instant of time. It is the
responsibility of the thread scheduler to give the thread, time to run.
A multi-threaded program allocates a fixed amount of time to each individual thread.
Each and every thread runs for a short while and then pauses and relinquishes the CPU to
another thread so that other threads can get a chance to run. When this happens, all such
threads that are ready to run, waiting for the CPU and the currently running thread lie in a
runnable state.
3. Blocked: The thread is waiting for a monitor lock to enter a synchronized block or
method. The thread will be in blocked state when it is trying to acquire a lock but
currently the lock is acquired by the other thread. The thread will move from the blocked
state to runnable state when it acquires the lock.
4. Waiting: The thread is waiting indefinitely for another thread to perform a specific action
(e.g., calling wait()).The thread will be in waiting state when it calls wait() method or
join() method. It will move to the runnable state when other thread will notify or that
thread will be terminated.
5. Timed Waiting: The thread is waiting for another thread to perform an action for a
specified period (e.g., sleep()).A thread lies in a timed waiting state when it calls a
method with a time-out parameter. A thread lies in this state until the timeout is
completed or until a notification is received. For example, when a thread calls sleep or a
conditional wait, it is moved to a timed waiting state.
6. Terminated: The thread has completed execution. A thread terminates because of either
of the following reasons:
i. Because it exits normally. This happens when the code of the thread has been
entirely executed by the program.
ii. Because there occurred some unusual erroneous event, like a segmentation fault
or an unhandled exception.
Joining a Thread
The join() method is used when one thread needs to wait for the completion of another. This
method blocks the calling thread until the thread on which join() is called has finished
executing.
Example:
Thread thread1 = new Thread(new MyRunnable());
thread1.start();
thread1.join(); // Main thread waits for thread1 to finish
System.out.println("Thread 1 has completed.");
Using join() ensures that the main thread will not proceed until thread1 has completed its task,
which can be crucial for maintaining the correct flow of execution in applications.
Thread Scheduler
The thread scheduler is a part of the Java Virtual Machine (JVM) that decides which thread to
run and for how long. It uses different scheduling algorithms to manage thread execution,
primarily focusing on:
Preemptive Scheduling: Higher priority threads can preempt lower priority threads,
ensuring that more important tasks get CPU time.
Time-Slicing: Each thread is given a fixed time slice to execute before the scheduler
switches to another thread, maintaining responsiveness.
The exact behavior of the thread scheduler can vary depending on the underlying operating
system.
Thread Priority
Java allows developers to set thread priorities, which influence the order in which threads are
scheduled. Thread priorities range from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY
(10), with Thread.NORM_PRIORITY (5) as the default.
java being completely object-oriented works within a multithreading environment in which
thread scheduler assigns the processor to a thread based on the priority of thread. Whenever we
create a thread in Java, it always has some priority assigned to it. Priority can either be given by
JVM while creating the thread or it can be given by the programmer explicitly.
Priorities in threads is a concept where each thread is having a priority which in layman’s
language one can say every object is having priority here which is represented by numbers
ranging from 1 to 10.
The default priority is set to 5 as excepted.
Minimum priority is set to 1.
Maximum priority is set to 10.
Here 3 constants are defined in it namely as follows:
i. public static int NORM_PRIORITY
ii. public static int MIN_PRIORITY
iii. public static int MAX_PRIORITY
Setting Thread Priority:
Thread thread = new Thread(new MyRunnable());
thread.setPriority(Thread.MAX_PRIORITY); // Sets the highest priority
thread.start();
While thread priority can suggest to the scheduler which thread should be given
preference, it does not guarantee execution order due to differences in operating
system scheduling.
Thread Synchronization
Synchronization is a critical aspect of multithreading, used to control access to shared resources
and prevent data inconsistency. When multiple threads access shared variables or resources
simultaneously, it can lead to unpredictable results. Java provides mechanisms for
synchronization:
Synchronized Methods:
synchronized void synchronizedMethod() {
// Code that accesses shared resources
Synchronized Blocks:
void method() {
synchronized(this) {
// Code that accesses shared resources
Using synchronized methods or blocks ensures that only one thread can access the synchronized
section at a time, effectively preventing race conditions.
Locks:
Java also includes the java.util.concurrent.locks package, which offers more sophisticated
locking mechanisms, such as ReentrantLock, allowing for greater control over synchronization.