Multithreading in Java is a process of executing multiple threads simultaneously to maximize CPU utilization. Each thread runs independently but shares the same memory space.
"Thread is a lightweight sub-process, the smallest unit of processing."
🔹 History
Version | Feature Added |
Java 1.0 | Basic Thread class, Runnable interface |
Java 1.2 | ThreadGroup enhancements |
Java 1.5 | java.util.concurrent package (Executor framework, locks, atomic variables, etc.) |
Java 1.6+ | Performance improvements in concurrency |
Java 1.7 | Fork/Join framework |
Java 1.8 | CompletableFuture, Lambda expressions in Runnable, Callable, etc. |
Java 9+ | Flow API (Reactive Streams), enhancements in CompletableFuture |
Java 19 | Virtual Threads (Project Loom Preview) |
🔹 Thread-Related Classes and Interfaces
Class / Interface | Purpose |
Thread | Direct class to create and control threads |
Runnable | Functional interface to define thread task |
Callable<V> | Like Runnable but returns result and can throw exceptions |
Future<V> | Represents the result of an asynchronous computation |
ExecutorService | Manages and executes tasks asynchronously |
Executors | Factory class for thread pools |
ThreadPoolExecutor | Core class behind Executors |
CompletableFuture | Async programming model with chaining support |
🔹 Thread Lifecycle
🔹 Thread Class Methods
Method | Description | Real-time Use Case |
start() | Begins thread execution | Launching background jobs like sending emails |
run() | Contains thread code (called by start) | Logic for file processing |
sleep(ms) | Pauses thread for milliseconds | Rate-limiting API calls |
join() | Waits for another thread to finish | Ensure file is downloaded before processing |
setName() / getName() | Set/get thread name | Logging & debugging |
setPriority() | Set execution priority (1-10) | Prioritize UI over background tasks |
interrupt() | Interrupts waiting or sleeping thread | Cancel long-running tasks |
isAlive() | Checks if thread is still running | Polling mechanism to monitor status |
yield() | Pause current thread and let others run | Used in thread scheduling experiments |
Thread
Class Methods Method | Description | Real-Time Use Case |
currentThread() | Returns reference to currently executing thread | Useful in logging current thread name in multi-threaded logs |
isInterrupted() | Checks if thread has been interrupted (without clearing flag) | Gracefully shutdown a long-running thread |
interrupted() (static) | Checks and clears the interrupted status | Cancel processing if thread was interrupted |
holdsLock(Object obj) (static) | Checks if current thread holds lock on given object | Used in advanced synchronization debugging |
checkAccess() | Throws SecurityException if current thread can’t modify the thread | Security check in sandboxed environments |
getState() | Returns enum Thread.State (NEW, RUNNABLE, etc.) | Monitoring thread health in admin dashboards |
getId() | Returns thread’s ID | Correlating logs or tracking in thread pools |
getStackTrace() | Returns stack trace elements of thread | Debugging and live thread dump analysis |
getThreadGroup() | Returns thread's group | Used in structured thread grouping (e.g., per module) |
setDaemon(true/false) | Sets as daemon thread | Background cleanup tasks that shouldn't block exit |
isDaemon() | Checks if it's a daemon thread | Decide if shutdown hook should wait or not |
🔹 Examples: Thread Class Methods
Use Case: Launching a background job (e.g., sending email)
class EmailSender extends Thread {
public void run() {
System.out.println("Sending email in background...");
}
}
public class StartExample {
public static void main(String[] args) {
EmailSender emailThread = new EmailSender();
emailThread.start(); // starts a new thread
System.out.println("Main thread continues...");
}
}
• run() – Defines the Thread Logic (usually invoked via start())
Use Case: File processing logic
class FileProcessor extends Thread {
public void run() {
System.out.println("Processing file in thread: " + Thread.currentThread().getName());
}
}
public class RunExample {
public static void main(String[] args) {
FileProcessor processor = new FileProcessor();
processor.run(); // runs on main thread, not recommended
}
}
⚠️ Always use .start() unless you want synchronous execution on main thread.
• sleep(ms) – Pause Execution Temporarily
Use Case: Rate limiting API calls
public class SleepExample {
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 5; i++) {
System.out.println("Calling API " + i);
Thread.sleep(1000); // wait 1 sec between calls
}
}
}
• join() – Wait for a Thread to Finish
Use Case: Ensure a file is downloaded before processing
class Downloader extends Thread {
public void run() {
try {
Thread.sleep(2000); // simulate download
System.out.println("Download complete");
} catch (InterruptedException e) {
System.out.println("Download interrupted");
}
}
}
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Downloader d = new Downloader();
d.start();
d.join(); // wait for download to finish
System.out.println("Now processing the file...");
}
}
• getName()/setName() – Name the Thread
Use Case: Debugging logs with thread names
public class NameExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
t.setName("Logger-Thread");
t.start();
}
}
• setPriority() – Control Thread Priority (1–10)
Use Case: Give higher priority to UI thread
public class PriorityExample {
public static void main(String[] args) {
Thread low = new Thread(() -> System.out.println("Low priority"));
Thread high = new Thread(() -> System.out.println("High priority"));
low.setPriority(Thread.MIN_PRIORITY);
high.setPriority(Thread.MAX_PRIORITY);
low.start();
high.start();
}
}
public void run() {
System.out.println("Processing file in thread: " + Thread.currentThread().getName());
}
}
public class RunExample {
public static void main(String[] args) {
FileProcessor processor = new FileProcessor();
processor.run(); // runs on main thread, not recommended
}
}
⚠️ Always use .start() unless you want synchronous execution on main thread.
• sleep(ms) – Pause Execution Temporarily
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= 5; i++) {
System.out.println("Calling API " + i);
Thread.sleep(1000); // wait 1 sec between calls
}
}
}
• join() – Wait for a Thread to Finish
Use Case: Ensure a file is downloaded before processing
public void run() {
try {
Thread.sleep(2000); // simulate download
System.out.println("Download complete");
} catch (InterruptedException e) {
System.out.println("Download interrupted");
}
}
}
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Downloader d = new Downloader();
d.start();
d.join(); // wait for download to finish
System.out.println("Now processing the file...");
}
}
• getName()/setName() – Name the Thread
Use Case: Debugging logs with thread names
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("Running in: " + Thread.currentThread().getName());
});
t.setName("Logger-Thread");
t.start();
}
}
• setPriority() – Control Thread Priority (1–10)
Use Case: Give higher priority to UI thread
public static void main(String[] args) {
Thread low = new Thread(() -> System.out.println("Low priority"));
Thread high = new Thread(() -> System.out.println("High priority"));
low.setPriority(Thread.MIN_PRIORITY);
high.setPriority(Thread.MAX_PRIORITY);
low.start();
high.start();
}
}
⚠️ Priority behavior depends on JVM/OS, not always guaranteed.
• interrupt() – Signal Thread to Stop (esp. if sleeping or waiting)
Use Case: Cancel a long-running task
public void run() {
try {
System.out.println("Task running...");
Thread.sleep(5000); // long sleep
System.out.println("Finished Task");
} catch (InterruptedException e) {
System.out.println("Task was interrupted!");
}
}
}
public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
LongTask task = new LongTask();
task.start();
Thread.sleep(1000);
task.interrupt(); // cancel the task
}
}
• isAlive() – Check If Thread Is Still Running
Use Case: Monitor long process
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try { Thread.sleep(2000); } catch (Exception e) {}
});
t.start();
while (t.isAlive()) {
System.out.println("Still running...");
Thread.sleep(500);
}
System.out.println("Thread finished");
}
}
• yield() – Hint to Scheduler to Let Other Threads Run
Use Case: Yield in CPU-bound thread experiments
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
Thread.yield(); // suggest pause to scheduler
}
};
Thread t1 = new Thread(task, "T1");
Thread t2 = new Thread(task, "T2");
t1.start();
t2.start();
}
}
🔹 Creating Threads – 3 Ways
(a) Extending Thread class
(b) Implementing Runnable interface
(c) Implementing Callable<V> with ExecutorService interfaces
🔹 Real-Time Use Cases of Multithreading
Use Case | Description |
Web Server | Handle each request in a separate thread |
UI + Background | UI thread for user interaction, background threads for heavy processing |
File Downloader | Download multiple files concurrently |
Video Streaming | Decode and buffer concurrently |
Scheduled Tasks | Reminders, health checks, cleanup jobs |
Gaming | Separate threads for rendering, AI, sound |
Banking | Async transaction processing & logging |
🔹 Executor Framework
Executor Framework provides a high-level replacement for managing Threads manually.
It uses thread pools to efficiently manage the execution of multiple asynchronous tasks.
Real-Time Use Cases
Use Case | Description |
Web Server | Handle multiple incoming HTTP requests concurrently |
Background Jobs | Process tasks like sending emails, generating reports |
Parallel Data Processing | Perform heavy computation in parallel threads |
Task Scheduling | Run recurring jobs like monitoring or auto-saving |
Key Components
Interface/Class | Description |
Executor | Basic interface with execute(Runnable) method |
ExecutorService | Extends Executor with lifecycle methods: submit(), shutdown() |
ScheduledExecutorService | Executes tasks after a delay or periodically |
ThreadPoolExecutor | Most powerful customizable thread pool |
Executors | Factory class for creating executor instances |
Key Executor Types from Executors
Factory
Method | Description |
Executors.newFixedThreadPool(n) | Fixed-size thread pool |
Executors.newCachedThreadPool() | Grows/shrinks threads based on demand |
Executors.newSingleThreadExecutor() | Single worker thread |
Executors.newScheduledThreadPool(n) | Fixed pool with scheduling support |
Basic Examples
Example – 1 : ExecutorService
with submit()
Example – 2 : Process multiple customer requests concurrently
Output:
Example – 3 : ScheduledExecutorService – Periodic Tasks (e.g., Auto Save, Cron Jobs)
Example – 4 : Custom ThreadPoolExecutor (Monitoring Thread Usage)
Example – 5 : A complete Spring Boot mini project that demonstrates:
- Using @Async for asynchronous methods
- Custom Executor configuration
- Exposing a REST endpoint that runs tasks in the background using the Executor Framework
1. Project Structure
spring-async-demo/
|── AsyncDemoApplication.java
|── config/
| |── AsyncConfig.java
|── controller/
| |── TaskController.java
|── service/
| |── TaskService.java
2. pom.xml – Add Spring Boot Dependencies
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-async-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
3. AsyncDemoApplication.java
package com.example.asyncdemo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableAsync // 👈 Enables async execution
public class AsyncDemoApplication {
public static void main(String[] args) {
SpringApplication.run(AsyncDemoApplication.class, args);
}
}
4. AsyncConfig.java – Custom ThreadPool Executor
package com.example.asyncdemo.config;
import org.springframework.context.annotation.*;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
@Configuration
public class AsyncConfig {
@Bean(name = "taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(5);
executor.setQueueCapacity(10);
executor.setThreadNamePrefix("Async-Worker-");
executor.initialize();
return executor;
}
}
5. TaskService.java – Long-Running Async Method
package com.example.asyncdemo.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
@Service
public class TaskService {
@Async("taskExecutor") // 👈 Executes in background thread
public void processTask(String taskName) {
System.out.println("Started: " + taskName + " on thread " + Thread.currentThread().getName());
try {
Thread.sleep(5000); // Simulate long-running job
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Completed: " + taskName);
}
}
6. TaskController.java – Trigger Async Method via REST
package com.example.asyncdemo.controller;
import com.example.asyncdemo.service.TaskService;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/tasks")
public class TaskController {
private final TaskService taskService;
public TaskController(TaskService taskService) {
this.taskService = taskService;
}
@PostMapping("/{name}")
public String triggerTask(@PathVariable String name) {
taskService.processTask(name);
return "Task '" + name + "' started!";
}
}
7. Run and Test
Start the application and hit:
curl -X POST http://localhost:8080/api/tasks/report1
You’ll see:
Task 'report1' started!
And in the console:
Started: report1 on thread Async-Worker-1
... (after 5 sec)
Completed: report1
You can hit the endpoint multiple times and see it run concurrently on different threads.
8. Summary
Feature | Description |
@EnableAsync | Enables async processing in Spring |
@Async | Marks method to run in background |
ThreadPoolTaskExecutor | Manages reusable threads |
REST Endpoint | Starts async task without blocking |
Raw Thread vs Executor Framework
Feature / Aspect | Raw Thread (Thread, Runnable) | Executor Framework (ExecutorService, Executors) |
Definition | Low-level abstraction for creating and managing a thread | High-level API for managing asynchronous task execution |
Thread Management | Manual (start, stop, join) | Automatically managed using thread pools |
Scalability | Poor (creates new thread for every task) | Excellent (reuses threads from pool) |
Resource Utilization | Expensive (overhead of creating many threads) | Efficient (threads are reused and managed) |
Code Simplicity | Complex to manage thread lifecycle, error handling | Cleaner and maintainable using task submission (submit(), invokeAll()) |
Task Submission | Directly using new Thread(task).start() | Submit via executor.execute() or executor.submit() |
Result Handling | No return values (without hacks like callbacks) | Supports Future<T> and Callable for result and exception handling |
Scheduling Support | Not available | Supported via ScheduledExecutorService |
Shutdown Mechanism | Manual thread interruption or join | Graceful shutdown with shutdown(), awaitTermination() |
Error Handling | Must manually handle exceptions inside run() method | Can capture via Future.get() or invokeAll() |
Use Case | Simple, short-lived tasks (e.g., quick prototypes) | Production-ready systems, concurrent APIs, background jobs, etc. |
Thread Naming/Tracking | Manual naming and monitoring | Advanced management and monitoring possible (ThreadPoolExecutor) |
Built-in Thread Pools | No | Yes — newFixedThreadPool(), newCachedThreadPool(), etc. |
Advanced Features | Not available | Available — scheduling, futures, lifecycle mgmt |
Raw Thread:
Executor Framework:
Criteria | Best Choice |
Lightweight prototypes | Raw Thread |
Scalable, production-grade apps | Executor Framework |
Need for task result/failure tracking | Executor Framework |
Repeating/scheduled tasks | Executor Framework (ScheduledExecutorService) |
-
Always shut down executor using
shutdown()
orshutdownNow()
to release threads. Use bounded queues (e.g.,
ArrayBlockingQueue
) to avoidOutOfMemoryError
.-
Avoid
newCachedThreadPool()
in production—uses unbounded threads. -
Use
Callable
+Future
to get results and handle exceptions. -
Prefer
Executors.newFixedThreadPool()
for CPU-bound tasks. -
Use
ScheduledExecutorService
for delayed or periodic tasks. -
Handle exceptions inside tasks to avoid silent failures.
-
Set thread names via custom
ThreadFactory
for easier debugging. -
Choose rejection policies wisely (
Abort
,CallerRuns
, etc.). -
Avoid long blocking operations in thread pool tasks.
-
Separate pools for long-running and short tasks to prevent starvation.
-
Use
CompletionService
for processing results as they complete. -
Monitor thread pool stats (
getPoolSize()
,getActiveCount()
). -
Leverage
CompletableFuture
for clean, async task composition.
🔹 CompletableFuture
REST endpoint /api/products/{id}
loads:
-
Product Info
-
Reviews
-
Seller Details
-
Shipping Estimate
👉 All done in parallel using CompletableFuture
.
AsyncCompletableApp.java
ProductDetails.java
ProductService.java
ProductController.java
Hit:
Output:
Benefit | Description |
Non-blocking | REST thread isn't blocked while fetching |
Fast & Efficient | Tasks run concurrently |
Clean Composition | Easy to compose multiple futures |
Production Ready | Works great for microservice aggregation |
Quick Comparison: Runnable vs Callable vs CompletableFuture
Feature | Runnable | Callable<T> | CompletableFuture |
Returns value | No | Yes | Yes |
Throws Exception | No | Yes | Yes |
Java Version | Java 1.0 | Java 5 | Java 8 |
Async support | Limited | Via Future | Fluent API chaining |
🔹 Thread-Safety and Synchronization
Thread-safety and synchronization are critical concepts in multi-threaded applications, especially in web apps where multiple requests can access shared data concurrently.
What is Thread-Safety?
A thread-safe class or method behaves correctly when multiple threads access it simultaneously, even without external synchronization.
What is Synchronization?
Synchronization is a mechanism to control access to shared resources to avoid data inconsistency (race conditions).
It can be achieved using:
-
synchronized
keyword -
Lock
interface -
Concurrent classes like
ConcurrentHashMap
,AtomicInteger
Real-Time Use Case (Spring Boot)
Let’s say we are building an API to track the number of visits to your website. We want to ensure the count is thread-safe.
🔴 Problem (Non-thread-safe)
Issue: Multiple threads can update count
at the same time → Race condition → Incorrect values.
Solution 1: Using synchronized
(basic synchronization)
Solution 2: Using AtomicInteger
(non-blocking, thread-safe)
Solution 3: Using ReentrantLock
(flexible locking)
Spring Boot Controller Example
Test It Concurrently
Hit the /api/visits/increment
endpoint with concurrent HTTP requests using Postman Runner, JMeter, or a custom script.
Summary
Approach | Thread-Safe | Blocking | Use Case |
synchronized | Yes | Yes | Simple use cases, small methods |
AtomicInteger | Yes | No | High concurrency counters, stats tracking |
ReentrantLock | Yes | Controlled | When fine-grained control is needed |
🔹 Fork/Join Framework
A Java framework (Java 7+) to split big tasks into smaller ones, run them in parallel, and combine the results — based on divide-and-conquer.
Example: Simple Fork/Join (Sum from 1 to 20)
Output:
🔹 Virtual Threads (Java 19+) – Lightweight Concurrency
🔹 Object Class Methods Used in Java Multithreading
These methods enable thread coordination and inter-thread communication via intrinsic locks (monitor) in Java.
Method | Description | Real-Time Use Case |
wait() | Causes the current thread to wait until another thread invokes notify()/notifyAll() on the same object | Used in Producer-Consumer, task queues, throttling systems |
wait(long timeout) | Waits for a specified time or until notified | Used in time-bound operations like polling with timeout |
wait(long timeout, int nanos) | Waits with nanosecond precision | Rarely used directly; mostly in low-level timing precision |
notify() | Wakes up a single thread waiting on the object | Waking up a blocked consumer after producer inserts item |
notifyAll() | Wakes up all threads waiting on the object’s monitor | Used when all threads may proceed after a condition is met |
• wait()
– Pause Thread Until NotifiedUse Case: Producer-Consumer style waiting
• notify()
– Wake Up One Waiting ThreadUse Case: Resuming a single consumer from multiple waiting threads
• notifyAll()
– Wake Up All Waiting ThreadsUse Case: Releasing all threads after condition is met (like reloading cache)
When to Use These:
Method | Use Case |
wait() | Thread should pause until condition met |
notify() | Resume one thread (e.g., single consumer) |
notifyAll() | Resume all threads (e.g., cache reload complete) |