Introduction of Threads:
• A thread in Java represents an independent path of execution within a program,
allowing for concurrent execution of multiple tasks.
• It is a lightweight unit of execution that shares the same memory space as other threads
within the same process.
• This shared memory enables efficient communication between threads, as objects in
one thread can directly access methods and data in another.
For example:
Imagine you are using your phone — you can listen to music, download a file, and chat at the
same time.
Each of these activities can be thought of as a thread working independently.
Creation:
Threads can be created in Java using two primary methods:
1) Extending the Thread class:
Create a class that extends [Link] and override its run() method with the code the
thread should execute.
O/P: Thread is running…
2) Implementing the Runnable interface:
Create a class that implements the [Link] interface and define the thread's task
within its run() method. An instance of this class is then passed to a Thread object.
O/P: Runnable thread is running…
Thread Lifecycle (States of a Thread):
A thread goes through several stages during its life:
1. New: The thread is created but not yet started.
→ Created using new Thread().
2. Runnable: The thread is ready to run but waiting for CPU time.
→ After calling start().
3. Running: The thread is currently executing its task.
4. Blocked/Waiting: The thread is paused temporarily (for example, waiting for input or
another thread to finish).
5. Terminated: The thread has completed its execution.
Each thread has a priority level from 1 (lowest) to 10 (highest). The default priority is 5.
Higher priority threads get more chances to run, but it depends on the operating system’s
thread scheduler — so the exact order is not guaranteed.
Multithreaded Programming in Java for Multi-Core Processors:
Multithreading means running two or more threads at the same time within a single program.
Each thread performs a separate task, but all threads share the same memory area.
This allows a program to perform many operations simultaneously — which makes it faster,
more efficient, and responsive.
For example:
• One thread can handle user input.
• Another can load data from the internet.
• Another can display results on the screen.
All of these run together without waiting for one another.
Threads on Multi-Core Processors
In modern computers, processors (CPUs) usually have multiple cores — for example, 2-core,
4-core, 8-core, etc.
Each core can run one thread at a time independently.
When we use multithreading in Java, the Java Virtual Machine (JVM) can assign different
threads to different cores, allowing true parallel execution.
So, on a quad-core processor, four threads can literally run at the same time, making the
program much faster than on a single-core system.
Example:
If you are processing images or analyzing data:
• On a single-core processor, all tasks happen one after another (sequentially).
• On a multi-core processor, each image or dataset can be handled by a separate thread,
all at once — this is parallelism.
O/P:
Thread-0 is running
Thread-1 is running
Thread-2 is running
Importance of Multi-threading on Multi-core Processor:
1. Parallel Execution and Performance
Multi-core processors can execute multiple instructions at once.
By running threads on separate cores, tasks are performed in parallel, which makes programs
faster and reduces execution time — especially for heavy computations.
2. Enhanced Responsiveness
In GUI-based applications, long tasks (like downloading or data processing) can run in separate
threads.
This keeps the main thread free, so the user interface remains smooth and responsive.
3. Efficient CPU Utilization
If one thread is waiting (for input or file access), another thread can use the CPU.
This ensures that no core remains idle and system resources are used efficiently.
4. Scalability
Multithreaded programs can easily adapt to systems with more cores.
As processors become more powerful, these programs can automatically take advantage of
additional cores without major changes.
5. Concurrent Task Handling
Many modern applications need to perform several tasks at once — like handling multiple
users or data streams.
Multithreading allows these tasks to run concurrently, improving speed and overall
performance.
Thread States:
A thread in Java can exist in any one of the following states at any given time. A thread lies
only in one of the shown states at any instant:
1. New State
2. Runnable State
3. Blocked State
4. Waiting State
5. Timed Waiting State
6. Terminated State
1. New State
• When we create a thread, it is in the new state.
• The thread is not yet started — its code hasn’t run yet.
• It is just created but not active.
Example: Thread t = new Thread(); → The thread is new.
2. Runnable State
• After calling start(), the thread goes to the runnable state.
• It means the thread is ready to run and waiting for the CPU.
• Sometimes it may be actually running, or waiting for its turn to run.
Example: When CPU time is given, it runs for a short time and then pauses to let
other threads run.
3. Blocked State
• A thread enters the blocked state when it is trying to get a lock (like when using
synchronized) but another thread is already holding it.
• It can’t continue until it gets that lock.
• Once the lock is free, it goes back to runnable state.
Example: Think of it like waiting for your turn to use a shared resource.
4. Waiting State
• A thread goes into waiting state when it calls wait() or join() without a time limit.
• It waits until another thread notifies it or finishes its work.
Example: One thread waits for another to complete before continuing.
5. Timed Waiting State
• A thread enters timed waiting when it waits for a specific time.
• It happens when methods like sleep(time) or wait(time) are used.
• After the given time ends (or it gets notified), it goes back to runnable.
Example: A thread sleeping for 5 seconds.
6. Terminated State
• A thread enters terminated (or dead) state when it finishes execution.
• It can also happen if an error or exception occurs.
Example: When the run() method completes, the thread dies.
Thread Priority – Synchronization:
Thread priority in Java influences the scheduling of threads by the operating system, suggesting
to the scheduler which threads are more important and should receive more CPU
time. However, it is important to understand that thread priority is a hint to the scheduler and
not a guarantee, as the actual behavior can be platform-dependent.
How Thread Priority Works in Java:
• Setting Priority: Threads in Java can have a priority level represented by an integer
value between 1 (minimum priority, Thread.MIN_PRIORITY) and 10 (maximum
priority, Thread.MAX_PRIORITY).
• The default priority is 5 (Thread.NORM_PRIORITY). You can set a thread's priority
using the setPriority() method and retrieve it using getPriority().
Example:
Thread myThread = new Thread(myRunnable);
[Link](Thread.MAX_PRIORITY); // Set to highest priority
int priority = [Link](); // Get current priority
• Scheduler's Role: The operating system's thread scheduler uses these priority hints to
decide which ready thread to run next. Higher-priority threads are generally given
preference and more CPU time compared to lower-priority threads.
Synchronization and Priority:
While thread priority influences when a thread runs, synchronization mechanisms control how
threads interact with shared resources. Synchronization, primarily achieved using
the synchronized keyword, ensures mutual exclusion, preventing multiple threads from
accessing a critical section of code simultaneously.
Deadlock and Race Suitations:
Deadlock and race conditions are common concurrency issues encountered in multithreaded
Java applications.
Race Condition:
A race condition occurs when the correctness of a program depends on the relative timing or
interleaving of operations of multiple threads. This happens when multiple threads access and
modify shared resources without proper synchronization, leading to unpredictable and
incorrect results.
Ex:
class Counter {
private int count = 0;
public void increment() {
count++; // This is a critical section
}
public int getCount() {
return count;
}
}
If multiple threads call increment() simultaneously without synchronization,
the count++ operation (which involves reading, modifying, and writing back the value) can
be interrupted, leading to lost updates and an incorrect final count.
Deadlock:
A deadlock is a specific type of race condition where two or more threads are blocked
indefinitely, each waiting for a resource held by another thread in the cycle. This creates a
circular dependency, preventing any of the involved threads from making progress.
Conditions for Deadlock:
For a deadlock to occur, four conditions must be met simultaneously:
1) Mutual Exclusion:
At least one resource must be held in a non-sharable mode, meaning only one thread can use it
at a time.
2) Hold and Wait:
A thread holding at least one resource is waiting to acquire additional resources held by other
threads.
3) No Preemption:
Resources cannot be forcibly taken from a thread; they must be released voluntarily by the
thread holding them.
4) Circular Wait:
A set of threads exists where each thread is waiting for a resource held by the next thread in
the set.
Inter - thread Communication in Java:
Inter-thread communication in Java allows threads to coordinate and share information. It uses
methods like wait(), notify(), and notifyAll() to enable synchronized execution and avoid
conflicts.
In Java, threads are like independent workers executing tasks in parallel. Sometimes, these
threads need to coordinate or share information to ensure tasks are completed in a
synchronized and efficient way. This coordination is called inter-thread communication. Inter-
thread communication allows threads to exchange messages or signals, enabling them to work
together seamlessly.
Why Do Threads Need To Communicate?
Threads need to communicate to coordinate their actions when they share resources or depend
on each other’s tasks. Without proper communication, threads might work independently,
leading to issues like data inconsistency, resource conflicts, or missed dependencies.
Producer-Consumer Problem:
Imagine a factory with two types of workers:
• Producers: Create items and place them on a conveyor belt (shared resource).
• Consumers: Pick items from the conveyor belt and package them.
Here’s why communication is crucial:
• Synchronization: If producers place items faster than consumers can pick them, the
conveyor might overflow. Similarly, if consumers try to pick items when the belt is
empty, they’ll face delays.
• Avoiding Conflicts: Producers and consumers must ensure they don’t simultaneously
access the same section of the belt, which could result in data corruption or operational
errors.
1. wait() Method
• Makes the current thread wait (pause) until another thread notifies it.
• While waiting, the thread releases the lock it holds on the object.
• It must be called inside a synchronized block or method.
Ex:
synchronized (sharedObject) {
[Link]("Thread waiting...");
[Link](); // waits until notified
[Link]("Thread resumed after wait");
}
2. notify() Method
• Wakes up one thread that is waiting on the same object’s monitor.
• If multiple threads are waiting, only one random thread is chosen.
• The awakened thread cannot run immediately — it must reacquire the lock first.
Ex:
synchronized (sharedObject) {
[Link]("Notifying one waiting thread...");
[Link](); // wakes up one waiting thread
3. notifyAll() Method
• Wakes up all threads that are waiting on the same object.
• They all become runnable, but only one thread will acquire the lock and run first
(others keep waiting for lock).
Ex:
synchronized (sharedObject) {
[Link]("Notifying all waiting threads...");
[Link]();