CH-01 : The Tale of Two Threads

The Beginning of a Great Project Jai and Veeru had been best friends since childhood. They had built sandcastles together, cracked math puzzles in school, and now, as software engineers, they were working on their most ambitious project yet: a personal finance tracking application. The plan was simple — Jai would handle the data processing and calculations, while Veeru would work on fetching and updating user transactions. They were confident, excited, and ready to conquer the tech world. But soon, chaos ensued. The Race Condition Catastrophe It all started on a quiet Monday morning. Jai was deep in his code, running a loop to sum up the user’s total expenses. At the same time, Veeru, unaware of Jai’s calculations, updated the transaction list in real-time. And just like that… their program crashed. “Jai! What did you do? My updates aren’t reflecting properly!” Veeru frowned. “What did I do? You just modified the data while I was still processing it!” Jai shot back. “But… we’re working together, right?” “Yeah, but not like this! It’s like trying to count sheep while someone keeps adding more in the middle!” Jai pulled out his laptop and ran their latest test: class SharedAccount { int balance = 0; void addExpense(int amount) { balance += amount; } } public class RaceConditionDemo { public static void main(String[] args) { SharedAccount account = new SharedAccount(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) account.addExpense(10); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) account.addExpense(10); }); t1.start(); t2.start(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final Balance: " + account.balance); } } Veeru stared at the output. Instead of 20,000, they were getting random values — sometimes 18,920, sometimes 19,570. “Wait, what? The final balance isn’t consistent?” “Exactly,” Jai said, “because both of us are modifying the same data at the same time without control. This is a race condition.” “Okay, so how do we fix it?” Jai smirked. “Time to introduce some order.” The Synchronization Spell — Preventing the Clash Jai took a deep breath. “We need to synchronize access to the shared resource so only one thread can modify the balance at a time.” class SharedAccount { int balance = 0; synchronized void addExpense(int amount) { // Lock applied! balance += amount; } } Veeru watched as Jai ran the updated code. Now, the final balance was always 20,000. “Whoa, so synchronized makes sure that only one of us works on the balance at a time!" Veeru said, amazed. “Exactly! It’s like locking the door while you’re in the room. I can’t come in and mess things up until you’re done.” “But wait… doesn’t this slow things down? If one of us is waiting, the other can’t do anything!” Jai nodded. “You’re catching on fast! That’s called thread blocking. Synchronization solves one problem but introduces another — performance bottlenecks.” Veeru frowned. “So what do we do?” Jai grinned. “We call the Executor.” The ExecutorService — The Manager of Chaos Jai leaned forward. “Instead of manually handling threads, we can use ExecutorService — think of it as a manager who assigns work to available workers efficiently.” He wrote down the improved version: import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; class SharedAccount { int balance = 0; synchronized void addExpense(int amount) { balance += amount; } } public class ExecutorServiceDemo { public static void main(String[] args) { SharedAccount account = new SharedAccount(); ExecutorService executor = Executors.newFixedThreadPool(2); for (int i = 0; i < 1000; i++) { executor.submit(() -> account.addExpense(10)); } executor.shutdown(); while (!executor.isTerminated()) {} System.out.println("Final Balance: " + account.balance); } } Veeru’s eyes widened. “So instead of us working on our own and messing things up, we now have a manager who keeps everything running smoothly?” Jai nodded. “Exactly. ExecutorService creates a pool of threads and reuses them efficiently instead of making new ones each time.” Veeru grinned. “Okay, I love this! But… can we do even better?” Jai smirked. “Of course. Let’s make it asynchronous.” CompletableFuture — The Magic of Parallel Execution Jai typed rapidly. “With CompletableFuture, we can run tasks in parallel, wait for them to finish, and even chain them together!" import java.util.concurrent.CompletableFuture; public class CompletableFutureDemo { public static void main(String[] ar

Apr 27, 2025 - 01:56
 0
CH-01 : The Tale of Two Threads

The Beginning of a Great Project

Jai and Veeru had been best friends since childhood. They had built sandcastles together, cracked math puzzles in school, and now, as software engineers, they were working on their most ambitious project yet: a personal finance tracking application.

The plan was simple — Jai would handle the data processing and calculations, while Veeru would work on fetching and updating user transactions. They were confident, excited, and ready to conquer the tech world.

But soon, chaos ensued.

The Race Condition Catastrophe

It all started on a quiet Monday morning. Jai was deep in his code, running a loop to sum up the user’s total expenses. At the same time, Veeru, unaware of Jai’s calculations, updated the transaction list in real-time.

And just like that… their program crashed.

“Jai! What did you do? My updates aren’t reflecting properly!” Veeru frowned.

“What did I do? You just modified the data while I was still processing it!” Jai shot back.

“But… we’re working together, right?”

“Yeah, but not like this! It’s like trying to count sheep while someone keeps adding more in the middle!”

Jai pulled out his laptop and ran their latest test:

class SharedAccount {
    int balance = 0;

    void addExpense(int amount) {  
        balance += amount;  
    }
}

public class RaceConditionDemo {
    public static void main(String[] args) {
        SharedAccount account = new SharedAccount();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) account.addExpense(10);
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) account.addExpense(10);
        });

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final Balance: " + account.balance);
    }
}

Veeru stared at the output. Instead of 20,000, they were getting random values — sometimes 18,920, sometimes 19,570.

“Wait, what? The final balance isn’t consistent?”

“Exactly,” Jai said, “because both of us are modifying the same data at the same time without control. This is a race condition.”

“Okay, so how do we fix it?”

Jai smirked. “Time to introduce some order.”

The Synchronization Spell — Preventing the Clash

Jai took a deep breath. “We need to synchronize access to the shared resource so only one thread can modify the balance at a time.”

class SharedAccount {
    int balance = 0;

    synchronized void addExpense(int amount) { // Lock applied!
        balance += amount;
    }
}

Veeru watched as Jai ran the updated code. Now, the final balance was always 20,000.

“Whoa, so synchronized makes sure that only one of us works on the balance at a time!" Veeru said, amazed.

“Exactly! It’s like locking the door while you’re in the room. I can’t come in and mess things up until you’re done.”

“But wait… doesn’t this slow things down? If one of us is waiting, the other can’t do anything!”

Jai nodded. “You’re catching on fast! That’s called thread blocking. Synchronization solves one problem but introduces another — performance bottlenecks.”

Veeru frowned. “So what do we do?”

Jai grinned. “We call the Executor.”

The ExecutorService — The Manager of Chaos

Jai leaned forward. “Instead of manually handling threads, we can use ExecutorService — think of it as a manager who assigns work to available workers efficiently.”

He wrote down the improved version:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class SharedAccount {
    int balance = 0;

    synchronized void addExpense(int amount) {
        balance += amount;
    }
}

public class ExecutorServiceDemo {
    public static void main(String[] args) {
        SharedAccount account = new SharedAccount();
        ExecutorService executor = Executors.newFixedThreadPool(2);

        for (int i = 0; i < 1000; i++) {
            executor.submit(() -> account.addExpense(10));
        }

        executor.shutdown();
        while (!executor.isTerminated()) {}

        System.out.println("Final Balance: " + account.balance);
    }
}

Veeru’s eyes widened. “So instead of us working on our own and messing things up, we now have a manager who keeps everything running smoothly?”

Jai nodded. “Exactly. ExecutorService creates a pool of threads and reuses them efficiently instead of making new ones each time.”

Veeru grinned. “Okay, I love this! But… can we do even better?”

Jai smirked. “Of course. Let’s make it asynchronous.”

CompletableFuture — The Magic of Parallel Execution

Jai typed rapidly. “With CompletableFuture, we can run tasks in parallel, wait for them to finish, and even chain them together!"

import java.util.concurrent.CompletableFuture;

public class CompletableFutureDemo {
    public static void main(String[] args) {
        CompletableFuture task1 = CompletableFuture.runAsync(() -> {
            System.out.println("Fetching transactions...");
        });

        CompletableFuture task2 = CompletableFuture.runAsync(() -> {
            System.out.println("Calculating expenses...");
        });

        task1.thenRun(task2).join();
        System.out.println("All tasks completed!");
    }
}

Veeru gasped. “This is amazing! It’s like we’re both working in parallel, without blocking each other!”

Jai nodded. “That’s the power of asynchronous programming. Tasks run independently, but we can still control their flow.”

Veeru grinned. “So, first we learned synchronization to fix conflicts, then ExecutorService to manage work, and finally CompletableFuture for full parallelism!”

Jai stretched his arms. “Exactly. And that, my friend, is how you handle multithreading and concurrency in Java.”

Veeru smiled. Their project was finally working smoothly.

But little did they know… the next challenge was lurking right around the corner — Deadlocks.