Files
HW-09-Advanced-Multithreading/REPORT.md
T

2.8 KiB

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<T>

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

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.