Topic 8. Multithreading Basics: core concepts of concurrency and threads
This topic is not Java-only. Concurrency is a universal systems concept used in servers, browsers, mobile apps, databases, and operating systems.
What multitasking means
Multitasking means multiple tasks make progress in overlapping time.
Concurrency: tasks are interleaved and progress together.Parallelism: tasks execute truly at the same time on multiple CPU cores.
Simple analogy:
- Concurrency: one cook switches between dishes.
- Parallelism: several cooks work at once.
Process vs Thread
Process: isolated running program instance with its own memory space.Thread: execution path inside a process.
Threads in one process share memory. This is efficient but introduces shared-state risks.
Why execution appears simultaneous
Even on one CPU core, OS scheduler rapidly rotates threads using time slices.
Each thread gets a short CPU quantum, then scheduler switches to another thread.
Why concurrency is hard
Difficulty is not starting threads; it is preserving correctness with shared data.
Typical failure classes:
Race condition: result depends on timing/interleaving.Visibility issue: one thread updates value, another thread does not observe it reliably.Deadlock: threads block each other permanently.Starvation: one thread rarely gets resources.Livelock: threads keep reacting but make no real progress.
Race condition example
private int counter = 0;
public void increment() {
counter++; // not atomic
}
counter++ includes read, compute, write steps. Interleaving between threads can lose updates.
Synchronization approaches
1. synchronized
private int counter = 0;
public synchronized void increment() {
counter++;
}
Benefits:
- mutual exclusion for critical section,
- visibility guarantees between threads.
2. Lock (ReentrantLock)
private final Lock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
Benefits:
- explicit lock lifecycle,
- richer APIs (
tryLock, timeout patterns).
3. Atomic types
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
Useful for simple lock-free counters.
Thread, Runnable, and ExecutorService in Java
Thread + Runnable
Runnable task = () -> System.out.println("Work");
Thread t = new Thread(task);
t.start();
Good for basics, not ideal for scalable production scheduling.
ExecutorService (thread pools)
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> doWork("A"));
pool.submit(() -> doWork("B"));
pool.shutdown();
Benefits:
- avoids creating a fresh thread per task,
- stabilizes runtime behavior under load,
- centralizes task lifecycle management.
CPU-bound vs IO-bound workloads
CPU-bound: dominated by computation.IO-bound: dominated by waiting (network/disk/DB).
Why this matters:
- CPU-bound workloads usually need limited thread count near core count.
- IO-bound workloads may benefit from larger pools because many threads wait on I/O.
Safe concurrent design rules
- Minimize shared mutable state.
- Prefer immutable objects.
- Keep critical sections short.
- Use concurrent structures when needed (
ConcurrentHashMap,BlockingQueue). - Prioritize correctness before optimization.
Debugging workflow
Concurrency bugs are often nondeterministic.
Practical steps:
- Run stress tests with repeated concurrent execution.
- Log state transitions with thread ids.
- Inspect thread dumps on hangs.
- Analyze lock contention hotspots.
Key takeaway
Concurrency is mainly a correctness discipline. The most reliable design is usually the one with the least shared mutable state.