0% found this document useful (0 votes)
21 views6 pages

Multithreading in Spring Boot Explained

Uploaded by

humtrupti
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
21 views6 pages

Multithreading in Spring Boot Explained

Uploaded by

humtrupti
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

🧵 1. What is a Thread?

👉 Thread = a lightweight unit of execution inside a program.


●​ Every Java program runs in at least one thread → called the main thread.​

●​ When we create more threads, multiple parts of the program can run at the same time.​

📌 Example:
●​ Imagine a food delivery app:​

○​ One thread shows the map.​

○​ One thread updates the order status.​

○​ One thread handles notifications.​


All run together → smoother experience.

🧵 2. Why use Threads?


Threads are useful when:

●​ You want to do multiple things at once (concurrency).​

●​ You don’t want the program to freeze while waiting for something.​

📌 Examples in real-world apps:


●​ Sending emails in background.​

●​ Downloading files while still browsing.​

●​ Processing big data while UI is still responsive.

🧵 3. How to Create Threads in Java


(a) Extending Thread class
class MyThread extends Thread {
public void run() {
[Link]("Thread running...");
}
}

public class Main {


public static void main(String[] args) {
MyThread t = new MyThread();
[Link](); // starts a new thread
}
}
(b) Implementing Runnable interface
class MyTask implements Runnable {
public void run() {
[Link]("Runnable thread running...");
}
}

public class Main {


public static void main(String[] args) {
Thread t = new Thread(new MyTask());
[Link]();
}
}

(c) Using Callable + Future (returns a value)


import [Link].*;

public class Main {


public static void main(String[] args) throws Exception {
ExecutorService service = [Link]();
Callable<Integer> task = () -> 10 + 20; // returns result
Future<Integer> future = [Link](task);
[Link]("Result: " + [Link]());
[Link]();
}
}

🧵 4. Thread Lifecycle
A thread moves through these states:

●​ New → created but not started.​

●​ Runnable → ready to run, waiting for CPU.​

●​ Running → actively executing.​

●​ Waiting/Timed waiting → paused, waiting for another thread or timer.​

●​ Terminated → finished execution.​

👉 Think of it like: Created → Ready → Running → Waiting → Finished.


🧵 5. Important Thread Methods
●​ sleep(ms) → pause for some time.​

●​ wait() → wait until another thread calls notify().​

●​ join() → one thread waits for another to finish.​

📌 Analogy:
●​ sleep → You nap for 5 minutes.​

●​ wait → You stop until your friend signals you.​

●​ join → You wait until your friend finishes homework.

🧵 6. Synchronization (Avoiding Problems)


Problem: If two threads change the same variable → data can get corrupted (Race Condition).

Solution:

●​ synchronized keyword → locks a method/block so only one thread can use it at a time.​

●​ ReentrantLock → advanced version of synchronized, gives more control (lock, unlock, tryLock).​

👉 Analogy: A toilet key: only one person can use it at a time 🚽.


🧵 7. Concurrency Utilities
Java gives us powerful tools:

●​ ExecutorService → manages a pool of threads. You submit tasks instead of manually creating threads.​

●​ Future → stores the result of a task that runs in the future.​

●​ CountDownLatch → wait until multiple tasks are finished (teacher waits for all students to finish).​

●​ CyclicBarrier → multiple threads wait until all are ready, then move forward together.

🧵 8. Common Problems
●​ Deadlock → two threads wait for each other forever.​

●​ Race Condition → multiple threads update same variable incorrectly.​

●​ Starvation → one thread never gets CPU because others dominate.


Multithreading in Spring Boot

(a) @Async annotation

●​ Runs a method in a separate thread.​

●​ Needs @EnableAsync on a config class.

@EnableAsync
@SpringBootApplication
public class MyApp { }

@Service
public class EmailService {
@Async
public void sendEmail(String user) {
[Link]("Sending email to " + user + " in thread: " + [Link]().getName());
}
}

(b) ThreadPoolTaskExecutor

●​ Instead of creating unlimited threads, Spring manages a pool.​

●​ You can configure in [Link]:​

[Link]-size=5
[Link]-size=10

(c) CompletableFuture in Spring

●​ Lets you run tasks in parallel and combine results.​

@Async
public CompletableFuture<String> getData() {
return [Link]("Hello");
}

(d) Scheduled Tasks

●​ Run jobs on a schedule.​

@Scheduled(fixedRate = 5000)
public void cleanUp() {
[Link]("Cleanup job running every 5 sec");
}​

Real-World Use Cases in Spring Boot
Background jobs → send emails, notifications.​
Parallel API calls → call multiple services at the same time.​
Scheduled jobs → cleanup, database backup.

Interview Questions

🔹 Basic Multithreading Questions


👉
1. What is multithreading in Java?​
Running many small tasks (threads) at the same time inside one program.

2. Difference between process and thread?

●​ Process = whole program, has its own memory.​

●​ Thread = small worker inside the program, shares memory with other workers.​

3. How do you create a thread in Java?

●​ Extend Thread class → override run().​

●​ Implement Runnable → pass to Thread.​

●​ Use Callable → when you need a return value.​

👉
4. What are thread states?​
A thread can be: New → Ready → Running → Waiting → Dead.

5. Difference between start() and run()?

●​ start() → actually creates a new thread.​

●​ run() → just runs like a normal method (no new thread).

🔹 Synchronization & Safety


👉
6. What is synchronization?​
Making sure only one thread uses a shared thing at a time.

7. Difference between synchronized method and block?

●​ Method → locks whole method.​

●​ Block → locks only a small part.​

👉
8. What is deadlock?​
When two threads wait for each other forever.

👉
9. What is race condition?​
When two threads change the same data at the same time → results are unpredictable.
👉
10. How to prevent deadlock?​
Always take locks in the same order or use timeout-based locks.

🔹 Process vs Thread
👉
●​ Process = a whole program that is running.​
Example: When you open Chrome, that’s a process.​
Each process has its own memory and works independently.​

👉
●​ Thread = a small worker inside a process.​
Example: Inside Chrome, one thread loads a webpage, another thread plays music, another handles
scrolling.​
All these threads share the same memory of Chrome.​

✅ So:
●​ Process = Big program​

●​ Thread = Small task inside the program

🔹 Thread Lifecycle in Java


A thread goes through different stages (states) in its life:

1.​ New → Thread object is created but not started yet. (Thread t = new Thread())​

2.​ Runnable (Ready to run) → After calling start(), thread is waiting for CPU.​

3.​ Running → CPU is executing the thread’s run() method.​

4.​ Waiting/Timed Waiting/Blocked → Thread is paused, waiting for something (like sleep(), wait(), or
waiting for a lock).​

5.​ Terminated (Dead) → Thread has finished running.​

👉 Think of it like:
●​ New → Born​

●​ Runnable → Ready for work​

●​ Running → Working​

●​ Waiting → Taking a break​

●​ Terminated → Done

Common questions

Powered by AI

Common concurrency problems in multithreaded environments include deadlock, race condition, and thread starvation. Deadlock occurs when two or more threads wait indefinitely for locks held by each other. This can be mitigated by acquiring locks in a consistent order or using timeout-based locks . A race condition arises when threads modify shared data concurrently, resulting in unpredictable outcomes; it can be addressed through synchronization or using concurrent collections . Thread starvation happens when a thread is perpetually denied access to resources, often mitigated by employing fair lock mechanisms or adjusting thread priorities to ensure balanced resource distribution . Addressing these issues requires understanding the intricacies of thread interactions and deliberately managing resource allocation and execution order.

The lifecycle of a thread in Java consists of several states: New, Runnable, Running, Waiting (or Timed Waiting/Blocked), and Terminated. The 'New' state signifies that a thread object is created but not yet started. The 'Runnable' state means the thread is ready to run and is waiting for CPU allocation. In the 'Running' state, the thread's run() method is being actively executed by the CPU. The 'Waiting' state indicates the thread is paused, typically waiting for another thread's action or for a specified time to pass (timed waiting). Finally, 'Terminated' implies that the thread has completed execution and is no longer active . Understanding this lifecycle is crucial for effective thread management and utilization within applications.

Java offers several methods for creating threads, including extending the Thread class, implementing the Runnable interface, and using the Callable interface with Future. Extending the Thread class involves creating a new subclass and overriding the run() method, allowing for direct manipulation of thread behavior. In contrast, implementing Runnable requires defining the run() method in a separate class and passing it to a Thread instance, promoting better separation of task and thread management. The Callable interface, used in conjunction with Future and an ExecutorService, allows for results from asynchronous computations, enhancing control and flexibility by enabling concurrent execution with return values . Thus, these methods cater to different needs—from simple concurrent execution to more sophisticated results handling.

Threading, when appropriately used, can enhance system performance by enabling concurrent execution of tasks, which utilizes CPU more effectively and improves application responsiveness. However, threading also increases resource utilization, as each thread consumes memory for its execution context and introduces overhead in terms of context switching. Excessive threading can lead to resource contention, where threads compete for CPU time and shared resources, ultimately diminishing system performance rather than enhancing it. Additionally, improper thread synchronization can lead to race conditions and deadlock, which further degrade application reliability and performance . Balancing the number of threads to the system's capabilities, often managed through thread pools and concurrency control mechanisms, is key to leveraging threading advantages while mitigating resource utilization challenges.

Using threads in a Java program allows for concurrent execution of tasks, which significantly enhances application performance by enabling multiple operations to occur simultaneously. This concurrency allows for a more responsive application, as the program does not need to wait for one task to finish before starting another. For instance, in a real-world application like a food delivery app, one thread can handle notifications, another can update order statuses, and another can display the map—all contributing to smoother user experiences . Additionally, threads can prevent the program from freezing while waiting for input/output operations, such as downloading files or sending emails .

The analogy used to describe synchronization in Java is likened to a "toilet key," where synchronization ensures that only one person (thread) can use the toilet (method/block) at a time . This analogy highlights the exclusivity and mutual exclusion aspect of synchronization, where access to shared resources is restricted to prevent conflict and ensure data integrity. Such an analogy is significant as it simplifies the understanding of complex synchronization concepts, making it more relatable and easier to grasp for developers, especially those new to multithreading. It emphasizes the critical need for access control in concurrent programming to avoid race conditions and ensure thread-safe operations.

Synchronization in Java multithreading is crucial for ensuring that only one thread accesses shared resources or critical sections of code at a time, preventing data corruption (race conditions). The 'synchronized' keyword is used to lock a method or block of code, allowing only one thread to execute it at any given time, thus preserving data integrity by ensuring atomic interactions with shared resources . 'ReentrantLock' provides more advanced synchronization capabilities, offering explicit lock and unlock methods, the ability to attempt locking (tryLock), and more flexible wait conditions, fitting scenarios where more complex locking patterns are needed. Both methodologies ensure mutual exclusion and controlled access to shared variables, thereby safeguarding concurrent application operations .

CompletableFuture in Spring Boot facilitates handling asynchronous tasks by providing a comprehensive API for managing asynchronous operations and their results. It supports running tasks in parallel, chaining operations, and combining tasks while simplifying error handling and coordination of asynchronous tasks. By leveraging CompletableFuture, applications can offload intensive processes to run concurrently without blocking main application threads, ensuring responsiveness. Tasks managed via CompletableFuture can return results or propagate exceptions seamlessly, enhancing error handling and reliability . Furthermore, CompletableFuture's integration with Spring's @Async framework enables seamless application of asynchronous programming with minimal configuration, resulting in more modular and performant code architectures suitable for modern scalable applications.

The ThreadPoolTaskExecutor in Spring Boot aids in managing task executions by creating a reusable pool of threads. This mechanism reduces the overhead associated with frequent thread instantiation and destruction, allowing for a more efficient allocation of CPU resources. By configuring core and maximum pool sizes, as well as queue capacities, ThreadPoolTaskExecutor can be tailored to the specific requirements of an application, balancing load and performance . It provides benefits like improved scalability and optimum resource utilization, especially in environments with high concurrency or asynchronous task demands, enhancing overall application resilience and performance while simplifying concurrency management.

Java's ExecutorService improves thread management by abstracting the complexities of thread lifecycle management and enhancing resource utilization. Instead of manually creating and managing individual threads, ExecutorService allows submitting tasks to be executed, automatically handling the creation, execution, and lifecycle of required threads as part of a thread pool. This approach ensures better control over concurrency, as the thread pool can be configured to match the application's performance needs, reducing overhead and enhancing scalability. It also simplifies managing asynchronous task execution and coordination, providing more robust and maintainable code as well as built-in methods for orderly shutdown and task termination . In essence, ExecutorService streamlines task execution by optimizing the allocation and scheduling of threads in a controlled manner.

You might also like