Quiz 2

Synchronization — Thread Safety and Coordination

1032 words
5 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# Synchronization — Thread Safety and Coordination ## 🎯 Learning Objectives - Use `synchronized` for mutual exclusion - Understand the `synchronized` method vs `synchronized` block - Use `wait()` and `notify()` for inter-thread communication - Use `ReentrantLock` and `Atomic` classes - Use `ExecutorService` for thr...

Synchronization — Thread Safety and Coordination

🎯 Learning Objectives

  • Use synchronized for mutual exclusion
  • Understand the synchronized method vs synchronized block
  • Use wait() and notify() for inter-thread communication
  • Use ReentrantLock and Atomic classes
  • Use ExecutorService for thread pool management

1. The Race Condition Problem

java
class Counter {
    private int count = 0;
    public void increment() { count++; }  // Read-Modify-Write (not atomic!)
}
// Two threads calling increment() 1000 times each:
// Thread A reads count (0), Thread B reads count (0)
// Thread A writes count (1)
// Thread B writes count (1) — loses an increment!
// Expected: 2000, Actual: may be less
count++ is NOT atomic. It's three operations: read, increment, write. Without synchronization, thread interleaving causes lost updates.

2. The synchronized Keyword

2.1 Synchronized Method

java
class SafeCounter {
    private int count = 0;
    public synchronized void increment() {
        count++;  // Now atomic — only one thread at a time
    }
    public synchronized int getCount() {
        return count;
    }
}
Every Java object has an intrinsic lock (monitor). synchronized acquires this lock. If another thread holds the lock, the thread blocks until the lock is released.

2.2 Synchronized Block (Finer Control)

java
class BankAccount {
    private double balance;
    private final Object lock = new Object();  // Dedicated lock object
    public void withdraw(double amount) {
        synchronized (lock) {  // Only synchronize critical section
            if (balance >= amount) {
                balance -= amount;
            }
        }
        // Other non-critical code outside synchronized block
    }
}

2.3 Static Synchronized Methods

Locks on the Class object, not an instance:
java
class SharedResource {
    private static int counter = 0;
    public static synchronized void increment() {
        counter++;  // Lock on SharedResource.class
    }
}

3. wait() and notify()

Used for inter-thread communication — one thread waits for a condition, another signals it.
java
class MessageQueue {
    private String message;
    private boolean empty = true;
    public synchronized String take() {
        while (empty) {
            try { wait(); }  // Release lock, wait for notification
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
        empty = true;
        notifyAll();  // Wake up waiting threads
        return message;
    }
    public synchronized void put(String msg) {
        while (!empty) {
            try { wait(); }
            catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        }
        empty = false;
        message = msg;
        notifyAll();
    }
}
Key rules:
  • wait(), notify(), notifyAll() must be called from a synchronized context
  • wait() releases the lock temporarily, reacquires before returning
  • notify() wakes one waiting thread; notifyAll() wakes all
  • Always use while loop (not if) for condition check (spurious wakeups)

4. java.util.concurrent Locks

4.1 ReentrantLock

java
Lock lock = new ReentrantLock();
public void doSomething() {
    lock.lock();
    try {
        // Critical section
    } finally {
        lock.unlock();  // Always release in finally!
    }
}

4.2 Atomic Variables (Lock-Free)

java
AtomicInteger count = new AtomicInteger(0);
// Thread-safe increment without synchronized
count.incrementAndGet();   // Atomically: count = count + 1
count.addAndGet(5);        // Atomically: count = count + 5
count.compareAndSet(10, 20); // If count is 10, set to 20

5. ExecutorService — Thread Pools

Manual thread management is error-prone. Use thread pools:
java
// Create thread pool
ExecutorService executor = Executors.newFixedThreadPool(4);
// Submit tasks
executor.submit(() -> System.out.println("Task 1"));
executor.submit(() -> System.out.println("Task 2"));
// Submit with return value
Future<Integer> future = executor.submit(() -> {
    Thread.sleep(1000);
    return 42;
});
// Get result (blocks until done)
Integer result = future.get();  // 42
// Shutdown
executor.shutdown();  // No new tasks, but existing ones complete
// executor.shutdownNow();  // Attempt to stop running tasks

6. Java vs Python: Concurrency

FeatureJavaPython
Thread creationThread, Runnablethreading.Thread
Synchronizationsynchronized, Lockthreading.Lock
Thread poolsExecutorServiceconcurrent.futures.ThreadPoolExecutor
Atomic opsAtomicInteger, etc.No built-in (use Lock)
GILNo GIL (true parallelism)GIL (limited parallelism)

7. Common Pitfalls

Pitfall 1: Deadlock

java
// Thread A: lock1 then lock2
// Thread B: lock2 then lock1
// Both wait forever!
Fix: Always acquire locks in the same order.

Pitfall 2: Synchronizing on String Literal

java
synchronized("lock") { }  // BAD — literals are shared across JVM!
Fix: Use new Object() or a dedicated lock field.

Pitfall 3: Calling wait() Outside Synchronized Block

Throws IllegalMonitorStateException.

8. Practice Questions

Q1: What does synchronized guarantee?
Answer: Mutual exclusion (only one thread executes the block at a time) and visibility (changes made by one thread are visible to others after exiting synchronized). Q2: What is a deadlock?
Answer: Two or more threads each waiting for a lock held by the other. Neither can proceed. Prevent by: lock ordering, timeout (tryLock), or deadlock detection. Q3: Difference between notify() and notifyAll()?
Answer: notify() wakes one arbitrary waiting thread. notifyAll() wakes all waiting threads. Use notifyAll() unless you're certain only one thread needs to wake — it's safer. Q4: Why use while loop with wait()?
Answer: To guard against spurious wakeups (threads can wake from wait() without notification) and to re-check the condition after reacquiring the lock. Q5: What is a volatile variable?
Answer: volatile ensures visibility: writes to a volatile variable are immediately visible to all threads. It does NOT provide atomicity. Use for flags, not for compound operations. Q6: What does ExecutorService.shutdown() do?
Answer: Prevents new tasks from being submitted. Already submitted tasks continue to execute. The JVM won't exit until all tasks complete. To wait for termination: executor.awaitTermination(timeout, unit). Q7: What is a race condition?
Answer: A situation where two or more threads access shared data simultaneously, and the outcome depends on the unpredictable timing of thread execution. Results in inconsistent or corrupted data. Q8: Can synchronized methods be interrupted while waiting for the lock?
Answer: No. Intrinsic lock acquisition is not interruptible. Use ReentrantLock.lockInterruptibly() for interruptible locking.

📐 Key Concepts

MechanismPurposeExample
synchronizedMutual exclusionsynchronized void m() { }
wait() / notify()Thread communicationProducer-consumer
ReentrantLockAdvanced lockinglock.lock(); try { } finally { lock.unlock(); }
AtomicIntegerLock-free thread safetyatomic.incrementAndGet()
ExecutorServiceThread pool managementExecutors.newFixedThreadPool(4)

🔗 Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.