# REPORT ### Hesam Ghazi ### 403222015 ## 1. Atomic Variables Atomic variables provide thread-safe single-variable operations performed atomically using CPU-supported compare-and-swap (CAS) instructions. Unlike ordinary variables, they prevent race conditions without explicit synchronization for simple operations. ## 2. Atomic Classes - `AtomicInteger` - `AtomicLong` - `AtomicBoolean` - `AtomicReference` **Use case:** `AtomicInteger` is commonly used as a thread-safe counter shared among multiple threads. ## 3. Locks vs Atomic Variables **Atomic variables** - Best for simple read-modify-write operations. - Non-blocking and usually faster under low contention. - Limited to simple operations. **Locks** - Suitable for protecting multiple variables or complex critical sections. - Easier to implement compound operations atomically. - Introduce blocking and context-switch overhead. ## Bonus Task ```java import java.util.concurrent.atomic.AtomicInteger; public class AtomicDemo { static int normal = 0; static AtomicInteger atomic = new AtomicInteger(0); public static void main(String[] args) throws Exception { Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(() -> { for (int j = 0; j < 100000; j++) { normal++; atomic.incrementAndGet(); } }); } for (Thread t : threads) t.start(); for (Thread t : threads) t.join(); System.out.println("Normal: " + normal); System.out.println("Atomic: " + atomic.get()); } } ``` Expected: `AtomicInteger` always prints 1000000, while `normal` is usually smaller because of race conditions. ## 4. Correct but Poor Performance A program may be race-free but still scale poorly because: 1. High lock contention forces threads to wait. 2. Excessive synchronization increases overhead. 3. False sharing and cache coherence traffic reduce CPU efficiency. 4. Frequent blocking decreases parallelism. ## 5. Why More Threads Can Hurt - **Context switching:** CPU spends time switching threads. - **Contention:** Threads compete for shared resources. - **Cache coherence:** Shared data invalidates CPU caches. - **Synchronization overhead:** Locks and coordination consume execution time. - Too many threads may exceed available CPU cores, reducing throughput. ## 6. Why Deadlocks Often Appear Only in Production Deadlocks depend on thread scheduling, which is nondeterministic. Testing usually explores only a small subset of possible execution orders, whereas production workloads create many timing combinations. Two strategies to expose deadlocks: 1. Perform stress tests with many threads and randomized execution timing. 2. Insert artificial delays (sleep/yield) around lock acquisition to increase unfavorable interleavings.