๐งญ 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
finallyblocks 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:
synchronizedkeyword - Explicit:
ReentrantLockclass
- Implicit:
- 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
- ๐จโ๐ซ Professor Slides (PDF)
- ๐งโ๐ซ TA Workshop Slides (PDF)
๐ ๏ธ Workshop & Assignments
๐ฌ Workshop: Advanced Multithreading
- ๐ WS-08-Advanced-Multithreading
- Structure: Exercises with skeleton files (
TODOmarkers) and solutions - Exercises Covered:
LockWorkshop: Resolving race conditions usingReentrantLockSynchronizedWorkshop: Resolving race conditions usingsynchronizedDeadlockPreventionWorkshop: Preventing deadlocks by enforcing global lock ordering based on unique Resource IDsTaylorSeries: Implementing high-precision math withBigDecimaland calculating Taylor series for sin(x) usingExecutorService
- 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):
- Atomic Variables: Explain purpose, differences from ordinary variables, name four
java.util.concurrent.atomicclasses with use cases - Locks vs Atomic Variables: Compare and identify scenarios for each
- Performance Under Contention: Explain why race-condition-free programs may perform poorly, discuss scalability factors
- Thread Scaling: Explain why more threads donโt always improve performance (context switching, contention, cache coherence, synchronization overhead)
- Deadlocks in Production: Explain why deadlocks appear in production not testing, describe strategies to expose them
- Atomic Variables: Explain purpose, differences from ordinary variables, name four
- 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,Atomicclasses,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)
- Phase 1 โ Thread-Safe Account State: Implement
- Evaluation: Correctness, robustness (no deadlocks), performance (independent accounts donโt block each other)
- Deadline: Friday, June 12 (22nd of Khordad)
๐ Repository Links
| Repository | Description |
|---|---|
| ๐ WS-08-Advanced-Multithreading | Workshop: Four exercises on locks, synchronization, deadlock prevention, and Taylor series |
| ๐ HW-09-Advanced-Multithreading | Assignment: Theoretical questions + Banking system with deadlock-free transfers |
๐ Additional Resources
- Java Concurrency Tutorial
- java.util.concurrent.locks Package
- Atomic Variables in Java
- Semaphore Documentation
โฉ Navigation
- โฌ ๏ธ Previous Topic: Hashing & Multithreading
- โก๏ธ Next Topic: Network
Tip :
The key to mastering advanced multithreading is understanding that different synchronization tools serve different purposes.
synchronizedis simple but coarse-grained;ReentrantLockoffers more flexibility;Semaphorecontrols access counts. For the banking assignment, start with simple synchronization, ensure correctness, then optimize. The most critical lesson: always release locks infinallyblocks and acquire locks in a consistent global order to prevent deadlocks. Remember, a correct solution is always more valuable than an optimized incorrect one!