Synchronization — Thread Safety and Coordination
1032 words
5 min read
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
synchronizedfor mutual exclusion - Understand the
synchronizedmethod vssynchronizedblock - Use
wait()andnotify()for inter-thread communication - Use
ReentrantLockandAtomicclasses - Use
ExecutorServicefor thread pool management
1. The Race Condition Problem
javaclass 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
javaclass 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)
javaclass 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:
javaclass 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.
javaclass 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 asynchronizedcontextwait()releases the lock temporarily, reacquires before returningnotify()wakes one waiting thread;notifyAll()wakes all- Always use
whileloop (notif) for condition check (spurious wakeups)
4. java.util.concurrent Locks
4.1 ReentrantLock
javaLock lock = new ReentrantLock(); public void doSomething() { lock.lock(); try { // Critical section } finally { lock.unlock(); // Always release in finally! } }
4.2 Atomic Variables (Lock-Free)
javaAtomicInteger 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
| Feature | Java | Python |
|---|---|---|
| Thread creation | Thread, Runnable | threading.Thread |
| Synchronization | synchronized, Lock | threading.Lock |
| Thread pools | ExecutorService | concurrent.futures.ThreadPoolExecutor |
| Atomic ops | AtomicInteger, etc. | No built-in (use Lock) |
| GIL | No 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
javasynchronized("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. UsenotifyAll()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:volatileensures 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. UseReentrantLock.lockInterruptibly()for interruptible locking.
📐 Key Concepts
| Mechanism | Purpose | Example |
|---|---|---|
synchronized | Mutual exclusion | synchronized void m() { } |
wait() / notify() | Thread communication | Producer-consumer |
ReentrantLock | Advanced locking | lock.lock(); try { } finally { lock.unlock(); } |
AtomicInteger | Lock-free thread safety | atomic.incrementAndGet() |
ExecutorService | Thread pool management | Executors.newFixedThreadPool(4) |
🔗 Cross-References
- Next: Swing GUI Programming Join Discord Previous10.1 Concurrency & ThreadsNext12.1 Swing GUI Programming