BIT401 Advanced Java Programming

Advanced Java ProgrammingUnit 510 min read

Multithreading & Concurrency: Threads, Synchronization, Deadlocks & RMI

Unit 5 of Advanced Java Programming covers multithreading concepts, thread lifecycle, synchronization mechanisms, race conditions, deadlocks, and Java’s concurrency utilities (e.g., ExecutorService, Callable, Future). It also introduces Remote Method Invocation (RMI) for distributed multithreading, with real-world exam

1. Introduction to Multithreading

Why Multithreading?

Multithreading allows a program to execute multiple threads concurrently within a single process, improving performance by utilizing CPU cores efficiently. Threads share the same memory space, reducing overhead compared to separate processes.

Thread vs. Process

Feature Thread Process
Definition Lightweight unit of execution Heavyweight unit of execution
Memory Shares memory with parent Has its own memory space
Creation Faster (lower overhead) Slower (higher overhead)
Communication Easier (shared memory) Requires IPC (e.g., pipes)
Isolation Less secure (shared resources) More secure (isolated)

Thread Lifecycle

stateDiagram-v2
    [*] --> New: Thread created (new)
    New --> Runnable: start() called
    Runnable --> Running: CPU schedules thread
    Running --> Blocked: Waits for I/O/monitor
    Running --> Waiting: Thread.sleep() or wait()
    Running --> Terminated: run() completes
    Blocked --> Runnable: Resource available
    Waiting --> Runnable: notify() called
start()CPU schedulesI/O/monitorsleep()/wait()run() completesResource availablenotify()NewRunnableRunningBlockedWaitingTerminated
Thread state transitions with triggers

Example: Creating Threads

// Method 1: Extending Thread class
class MyThread extends Thread {
    public void run() {
        System.out.println("Thread running");
    }
}

// Method 2: Implementing Runnable interface (preferred)
class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable thread running");
    }
}

public class Main {
    public static void main(String[] args) {
        // Using Thread class
        MyThread t1 = new MyThread();
        t1.start();

        // Using Runnable interface
        Thread t2 = new Thread(new MyRunnable());
        t2.start();
    }
}

Trace of t1.start() and t2.start():

Step Action State of t1 State of t2
1 t1.start() called Runnable New
2 t2.start() called Running Runnable
3 t1.run() completes Terminated Running
4 t2.run() completes Terminated Terminated

2. Thread Synchronization

Race Condition Problem

When multiple threads access shared data without synchronization, they may interfere, leading to inconsistent results.

10110203
Race condition: Thread 1 increments to [1,0,0,0], Thread 2 overwrites to [1,1,0,0] (lost update)
10010203
Thread 1 reads count=0 (initial state before increment)
00010203
Shared counter array (initial state: all zeros)

Example: Shared Counter (Race Condition)

class Counter {
    private int count = 0;

    public void increment() {
        count++; // Not thread-safe!
    }

    public int getCount() {
        return count;
    }
}

Trace of Race Condition:

Thread 1 (T1) Thread 2 (T2) Final count
count = 0
count = 1
count = 1
count = 2 Expected: 2
Actual: 1 (Lost update!)

Solution: synchronized Keyword

public synchronized void increment() {
    count++; // Thread-safe
}

Trace After Synchronization:

T1 Action T2 Action count
Locks increment() Waits 0
count = 1 Waits 1
Releases lock Locks increment() 1
count = 2 2

Other Synchronization Methods

  1. wait()/notify(): Used for thread coordination.
  2. ReentrantLock: More flexible than synchronized.
  3. AtomicInteger: Thread-safe counter (no locks needed).

3. Deadlocks and Starvation

Deadlock Scenario

A deadlock occurs when two or more threads are blocked forever, each waiting for a resource held by the other.

holdswaitingholdswaitingThread 1Thread 2Resource AResource B
Circular wait deadlock (4 necessary conditions)
lockedwaitinglockedwaitingAccount AAccount BThread 1Thread 2
Deadlock: Circular wait between threads and accounts

Example: Deadlock in Bank Transactions

class Account {
    private int balance;

    public synchronized void transfer(Account to, int amount) {
        if (this == to) return; // Avoid self-transfer

        // Deadlock risk: Locking two accounts in different orders!
        this.withdraw(amount);
        to.deposit(amount);
    }

    public synchronized void withdraw(int amount) { /* ... */ }
    public synchronized void deposit(int amount) { /* ... */ }
}

Trace of Deadlock:

Thread 1 (T1) Thread 2 (T2)
Locks Account A Locks Account B
Tries to lock Account B (waits) Tries to lock Account A (waits)
Deadlock! Deadlock!

How to Avoid Deadlocks?

  1. Lock Ordering: Always acquire locks in a predefined order.
  2. Timeouts: Use tryLock(timeout).
  3. Deadlock Detection: Tools like ThreadMXBean.

4. Java Concurrency Utilities

Executor Framework

Manages thread pools efficiently.

ExecutorService executor = Executors.newFixedThreadPool(4);

executor.submit(() -> {
    System.out.println("Task running in thread pool");
});

executor.shutdown();

Callable and Future

Allows threads to return results.

Future<Integer> future = executor.submit(() -> {
    return 42; // Returns Future<Integer>
});

System.out.println("Result: " + future.get()); // Blocks until done

5. Remote Method Invocation (RMI)

What is RMI?

RMI allows objects to invoke methods on remote Java objects (distributed multithreading).

Example: Remote Calculator

// Remote Interface
interface Calculator extends Remote {
    int add(int a, int b) throws RemoteException;
}

// Server Implementation
public class CalculatorImpl extends UnicastRemoteObject implements Calculator {
    public CalculatorImpl() throws RemoteException {}

    public int add(int a, int b) {
        return a + b;
    }
}

// Client Code
Calculator calc = (Calculator) Naming.lookup("rmi://localhost/Calculator");
int result = calc.add(5, 3); // Calls remote method!

Trace of RMI Call:

Step Action Location
1 Client calls Naming.lookup() Client JVM
2 RMI Registry finds server RMI Registry
3 Server executes add(5, 3) Server JVM
4 Result (8) sent back Client JVM

In the Real World

  1. eSewa (Nepal):

    • Uses multithreading to handle thousands of online payments simultaneously (e.g., electricity bills, fines).
    • Concurrency: Multiple threads process transactions in parallel to avoid delays.
  2. Khalti (Nepal):

    • Thread pools manage high-frequency API calls (e.g., merchant transactions, refunds).
    • Synchronization: Prevents double-spending by locking accounts during transfers.
  3. WhatsApp (Global):

    • Multithreading processes messages, media uploads, and notifications concurrently.
    • RMI-like systems (though not pure Java RMI) enable cross-device sync (e.g., phone ↔ web).
  4. Nepal Rastra Bank (NRB) Loan Processing:

    • Deadlock avoidance: When multiple branches process loans, lock ordering prevents deadlocks in database updates.
  5. Pathao (Nepal) Ride Matching:

    • Thread pools match drivers to riders in real-time (e.g., 10,000+ requests/sec during festivals).
    • Atomic counters track available drivers without race conditions.

Exam Tip

  1. Thread Lifecycle: Always draw the state diagram and explain transitions (e.g., start() moves from New to Runnable).
  2. Synchronization: Compare synchronized vs. ReentrantLock in terms of flexibility and overhead.
  3. Deadlocks: Always mention the 4 conditions (mutual exclusion, hold & wait, no preemption, circular wait) and how to break them.
  4. RMI: Know the steps (registry lookup, stub/skeleton, remote call) and exceptions (RemoteException, NotBoundException).
  5. Code Traces: For every example, show a table of thread states (like the counter example above).

Visual Summary of Key Concepts:

In the real world

  • eSewa (Nepali payment system) uses thread pools in its backend to handle simultaneous payment requests (e.g., electricity bills, traffic fines). Each payment thread processes transactions independently, reducing wait times during peak hours (e.g., 6–9 PM).

  • Nepal Rastra Bank’s core banking system employs synchronized blocks to prevent race conditions when updating account balances across multiple ATMs. For example, if two users withdraw ₹10,000 from the same account simultaneously, the bank’s synchronized methods ensure the final balance is correct.

  • Pathao’s ride-matching algorithm uses thread-safe data structures (e.g., ConcurrentHashMap) to track driver availability in real-time. When a user requests a ride, multiple threads check driver locations, but the system avoids deadlocks by enforcing a lock order (e.g., always locking driverLocation before rideRequest).

Based on the TU BIT syllabus for Advanced Java Programming (BIT401), unit 5.

Discussion

Loading…