Creating Threads
Java can create work using Thread, Runnable, Callable, and executors. Implementing Runnable is often preferred over extending Thread because Java supports only one concrete superclass.
Visibility vs Mutual Exclusion
volatile gives visibility: reads see the latest written value. It does not make compound operations like count++ atomic.
synchronized gives mutual exclusion and visibility by acquiring and releasing a monitor lock.
Thread-Safety Strategies
| Strategy | Example |
|---|---|
| Stateless design | No shared mutable fields |
| Immutability | final fields, no setters |
| Thread-local state | ThreadLocal<T> |
| Atomic classes | AtomicInteger, AtomicReference |
| Concurrent collections | ConcurrentHashMap, CopyOnWriteArrayList |
Interview Warning
Do not say volatile is a replacement for synchronized. Volatile is excellent for flags; synchronized or atomic classes are needed for safe compound updates.
Concurrency Code Examples
volatile for Visibility
class Worker {
private volatile boolean running = true;
public void stop() {
running = false;
}
public void run() {
while (running) {
// work until another thread changes the flag
}
}
}
synchronized for Mutual Exclusion
class Wallet {
private int balance;
public synchronized void deposit(int amount) {
balance += amount;
}
public synchronized int getBalance() {
return balance;
}
}
AtomicInteger for Safe Counters
class VisitCounter {
private final AtomicInteger count = new AtomicInteger();
public int increment() {
return count.incrementAndGet();
}
}
Interview Scenario Practice
Scenario 1: Stop Flag Not Seen by Worker Thread
Scenario: One thread sets a boolean stop flag to false, but another thread keeps running.
Strong answer: Use volatile for a simple visibility flag, or use higher-level cancellation tools when working with executors.
Why it works: volatile tells Java that reads should see the latest write from main memory.
Common mistake: Thinking volatile makes all operations atomic. It does not.
Scenario 2: HashMap Frequency Counter Failed
Scenario: A HashMap frequency counter gives wrong counts when multiple threads update it.
Strong answer: A normal HashMap is not thread-safe, and get then put is a compound operation. Use ConcurrentHashMap with merge, synchronize the critical section, or use atomic counters.
Why it works: Concurrent updates need atomicity, not just visibility.
Common mistake: Adding volatile to the map reference and expecting the internal updates to become safe.
Scenario 3: Shared Counter Under Load
Scenario: A shared count++ loses increments during load testing.
Strong answer: Use AtomicInteger.incrementAndGet(), LongAdder for high-contention counters, or a synchronized block when the update belongs to a larger critical section.
Why it works: count++ is read, add, and write. Those steps can interleave across threads.
Common mistake: Saying an int update is always safe because reading an int is small.