Lab 2 · Synchronization Basics

Difficulty: Beginner Time: 45 minutes Topics: Synchronized, volatile, race conditions, Atomic types


Objectives

  • ✅ Reproduce a race condition
  • ✅ Fix with synchronized keyword
  • ✅ Fix with volatile
  • ✅ Fix with AtomicInteger

Starter Code

public class Lab02Synchronization {

    // Exercise 1: Demonstrate race condition
    static class Counter {
        public int count = 0;  // Shared, not synchronized

        public void increment() {
            count++;  // NOT atomic!
        }
    }

    static void exercise1_RaceCondition() {
        Counter counter = new Counter();
        Thread[] threads = new Thread[10];

        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
               for (int j = 0; j < 10000; j++) {
                   counter.increment();
               }
            });
        }

        // TODO: Start all threads
        // TODO: Join all threads
        // TODO: Print final count (should be 100000, but won't be!)
    }

    // Exercise 2: Fix with synchronized
    static class CounterSynchronized {
        private int count = 0;

        public synchronized void increment() {
            count++;
        }

        public synchronized int getCount() {
            return count;
        }
    }

    static void exercise2_WithSynchronized() {
        // TODO: Repeat exercise 1 with CounterSynchronized
        // Result should be exactly 100000
    }

    // Exercise 3: Fix with volatile (won't work for increment!)
    static class CounterVolatile {
        public volatile int count = 0;

        public void increment() {
            count++;  // Still NOT atomic, but visibility is guaranteed
        }
    }

    static void exercise3_WithVolatile() {
        // TODO: Repeat with CounterVolatile
        // Result will still be wrong - volatile doesn't help with ++
    }

    // Exercise 4: Fix with AtomicInteger (The right way)
    static void exercise4_WithAtomicInteger() {
        java.util.concurrent.atomic.AtomicInteger counter = new java.util.concurrent.atomic.AtomicInteger(0);
        Thread[] threads = new Thread[10];

        for (int i = 0; i < 10; i++) {
            threads[i] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    counter.incrementAndGet();  // Atomic
                }
            });
        }

        // TODO: Start, join, print (should be 100000)
    }

    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Lab 02: Synchronization Basics ===\n");

        exercise1_RaceCondition();
        exercise2_WithSynchronized();
        exercise3_WithVolatile();
        exercise4_WithAtomicInteger();
    }
}

Key Concepts

  • Race Condition: count++ is three operations: read, add, write
  • Synchronized: Only one thread in critical section at a time
  • Volatile: Visibility, but not atomicity
  • Atomic: Lock-free atomicity using CAS

References