๐Ÿงญ Topic: Advanced Multithreading

Quick Overview :

This topic covers advanced multithreading concepts including race conditions, thread pooling, locking mechanisms (ReentrantLock, synchronized), semaphores, mutex, and deadlock prevention. It focuses on building thread-safe, scalable concurrent applications with proper synchronization strategies.


๐Ÿ“Œ Covered in This Topic

Concurrency Issues

Race Condition

  • Definition: Occurs when multiple threads access and modify shared data concurrently without proper synchronization
  • Common Symptoms:
    • Unexpected output values
    • Non-deterministic behavior
    • Hard-to-reproduce bugs
    • Lost updates

Race Condition Example (Unsafe)

public class RaceCondition {
    private static int counter = 0;
    public static void unsafeIncrement() {
        for (int i = 0; i < 10000; i++) {
            counter++; // Race condition here!
        }
    }
}

Race Condition Example (Safe with Synchronization)

public class RaceCondition {
    private static int counter = 0;
    public static void safeIncrement() {
        for (int i = 0; i < 10000; i++) {
            synchronized(RaceCondition.class) {
                counter++; // Protected
            }
        }
    }
}

Thread Pooling

  • Key Benefits:
    • Manages a pool of worker threads
    • Reduces overhead of thread creation
    • Controls resource utilization
    • Improves performance for task-based workloads

Thread Pool Example

import java.util.concurrent.*;
 
public class ThreadPoolExample {
    public static void main(String[] args) {
        // Create a pool with 4 threads
        ExecutorService pool = Executors.newFixedThreadPool(4);
        
        // Submit multiple tasks
        for (int i = 0; i < 4; i++) {
            final int taskId = i;
            pool.execute(() -> {
                System.out.println("Task " + taskId);
            });
        }
        // Shutdown the pool properly
        pool.shutdown();
    }
}

Locking Mechanisms

Lock Interface

  • Key Features:
    • Explicit locking mechanism
    • More flexible than synchronized
    • Supports timeout and interruptible waits
    • Provides non-blocking tryLock()
  • Critical: Always release locks in finally blocks to prevent deadlocks

Lock Example

import java.util.concurrent.locks.*;
 
public class LockExample {
    private static int counter = 0;
    private static Lock lock = new ReentrantLock();
    
    public static void increment() {
        for (int i = 0; i < 10000; i++) {
            lock.lock(); // Acquire the lock
            try {
                counter++; // Critical section
            } finally {
                lock.unlock(); // Release in finally
            }
        }
    }
}

Mutex (Mutual Exclusion)

  • Mutual exclusion object - allows only one thread at a time
  • In Java:
    • Implicit: synchronized keyword
    • Explicit: ReentrantLock class
  • Special case of semaphore with permit count = 1

Synchronized Example (Implicit Mutex)

public class SynchronizedExample {
    private int count = 0;
    
    public synchronized void increment() {
        count++; // Protected by implicit mutex
    }
    // Equivalent to explicit locking with ReentrantLock
}

Reentrant Lock

  • Key Features:
    • Can be reacquired by owning thread
    • Maintains lock count
    • Supports recursive method calls
    • Must be unlocked same number of times as locked

ReentrantLock Example

import java.util.concurrent.locks.*;
 
public class RLockExample {
    private static ReentrantLock lock = new ReentrantLock();
    
    public static void recursive(int depth) {
        if (depth == 0) return;
        
        lock.lock(); // Acquire the lock
        try {
            System.out.println("Depth: " + depth);
            // Recursive call with same lock
            recursive(depth - 1);
        } finally {
            lock.unlock(); // Release the lock
        }
    }
}

Semaphore

  • Key Features:
    • Controls access with permits
    • Limits concurrent access
    • Useful for resource pools
    • Can simulate mutex (permits=1)
    • Supports fairness policy
  • Common Use Case: Connection pool limiting with database connections

Semaphore Example

import java.util.concurrent.*;
 
public class SemaphoreExample {
    // Allow only 2 concurrent accesses
    private static Semaphore sem = new Semaphore(2);
    
    public static void accessResource() {
        try {
            sem.acquire(); // Get a permit
            try {
                System.out.println("Resource access");
                // Simulate work with resource
                Thread.sleep(1000);
            } finally {
                sem.release(); // Return permit
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Design for Thread Safety

  • Minimize shared mutable state
  • Use immutable objects when possible
  • Use higher-level concurrency utilities (Executors, concurrent collections)
  • Prefer atomic variables over locks for simple counters
  • Document thread-safety guarantees

Avoid Common Pitfalls

  • Deadlocks: Acquire locks in consistent order
  • Thread starvation: Design for fairness
  • Memory leaks: Clean up thread resources
  • Context switching overhead: Balance thread count

๐Ÿ“‘ Slides & Materials


๐Ÿ› ๏ธ Workshop & Assignments

๐Ÿ’ฌ Workshop: Advanced Multithreading

  • ๐Ÿ“‚ WS-08-Advanced-Multithreading
  • Structure: Exercises with skeleton files (TODO markers) and solutions
  • Exercises Covered:
    1. LockWorkshop: Resolving race conditions using ReentrantLock
    2. SynchronizedWorkshop: Resolving race conditions using synchronized
    3. DeadlockPreventionWorkshop: Preventing deadlocks by enforcing global lock ordering based on unique Resource IDs
    4. TaylorSeries: Implementing high-precision math with BigDecimal and calculating Taylor series for sin(x) using ExecutorService
  • Requirements: Java 17+, Maven 3.6+
  • Build: mvn clean compile
  • Run: mvn exec:java -Dexec.mainClass="workshop.solutions.ClassName"

๐Ÿงฎ Assignment: Banking System

  • ๐Ÿ“‚ HW-09-Advanced-Multithreading
  • Theoretical Questions (Answers.md):
    1. Atomic Variables: Explain purpose, differences from ordinary variables, name four java.util.concurrent.atomic classes with use cases
    2. Locks vs Atomic Variables: Compare and identify scenarios for each
    3. Performance Under Contention: Explain why race-condition-free programs may perform poorly, discuss scalability factors
    4. Thread Scaling: Explain why more threads donโ€™t always improve performance (context switching, contention, cache coherence, synchronization overhead)
    5. Deadlocks in Production: Explain why deadlocks appear in production not testing, describe strategies to expose them
  • Practical Project โ€“ Banking System:
    • Phase 1 โ€“ Thread-Safe Account State: Implement deposit(), withdraw(), getBalance() with proper synchronization
    • Phase 2 โ€“ Atomic Transfers: Implement transfer() as fully atomic operation, no partial updates, no lost money
    • Phase 3 โ€“ Deadlock-Free Design: Handle concurrent transfers, prevent cyclic locking, pass stress tests
    • Allowed Tools: synchronized, ReentrantLock, ReentrantReadWriteLock, Condition, Atomic classes, java.util.concurrent
    • Restrictions: No busy waiting, no modifying test files or method signatures, no additional worker threads
    • Bonus: Implement conditional waiting for insufficient funds (no busy waiting, no starvation)
  • Evaluation: Correctness, robustness (no deadlocks), performance (independent accounts donโ€™t block each other)
  • Deadline: Friday, June 12 (22nd of Khordad)

RepositoryDescription
๐Ÿ“‚ WS-08-Advanced-MultithreadingWorkshop: Four exercises on locks, synchronization, deadlock prevention, and Taylor series
๐Ÿ“‚ HW-09-Advanced-MultithreadingAssignment: Theoretical questions + Banking system with deadlock-free transfers

๐ŸŒ Additional Resources


โฉ Navigation


Tip :

The key to mastering advanced multithreading is understanding that different synchronization tools serve different purposes. synchronized is simple but coarse-grained; ReentrantLock offers more flexibility; Semaphore controls access counts. For the banking assignment, start with simple synchronization, ensure correctness, then optimize. The most critical lesson: always release locks in finally blocks and acquire locks in a consistent global order to prevent deadlocks. Remember, a correct solution is always more valuable than an optimized incorrect one!