Java Concurrency Complete

Threads & Concurrency

Unlike single-threaded JavaScript, Java has real OS threads and true parallelism. That power comes with the classic hazards: race conditions, deadlocks, and shared mutable state.

Java 21 Interview Key 5 Topics
01

Threads & Runnable

A thread is an independent path of execution. The idiomatic way to define work is a Runnable (a functional interface), which you hand to a Thread to run in parallel with the rest of the program.

Java — Threads
// Runnable as a lambda — the preferred style
Runnable task = () -> System.out.println("running on " + Thread.currentThread().getName());

Thread t = new Thread(task);
t.start();          // starts a NEW thread — do not call run() directly
t.join();           // wait for t to finish before continuing

// Multiple threads run concurrently
for (int i = 0; i < 3; i++) {
    int id = i;
    new Thread(() -> System.out.println("worker " + id)).start();
}
Call start(), never run() — run() just executes the body on the current thread with no parallelism. Only start() spins up a new thread.
02

ExecutorService — Thread Pools

Creating a raw Thread per task is wasteful. An ExecutorService manages a reusable pool of threads and a queue of work — the standard way to run many tasks concurrently.

Java — ExecutorService & Future
import java.util.concurrent.*;

ExecutorService pool = Executors.newFixedThreadPool(4);

// submit returns a Future — a handle to the eventual result
Future<Integer> future = pool.submit(() -> {
    return 2 + 2;
});

int result = future.get();     // blocks until the task finishes: 4

// run many tasks, wait for all
for (int i = 0; i < 10; i++) {
    int id = i;
    pool.submit(() -> System.out.println("job " + id));
}
pool.shutdown();          // stop accepting new work, finish queued tasks
✓Java 21 added virtual threads (Executors.newVirtualThreadPerTaskExecutor()) — lightweight threads where millions can run at once, making blocking I/O cheap without the async/await style JavaScript needs.
03

synchronized & Shared State

When two threads write the same variable, the result is a race condition. synchronized makes a method or block run one-thread-at-a-time on a given lock, so updates don't interleave.

Java — Race Condition & the Fix
class Counter {
    private int count = 0;

    // WITHOUT synchronized, count++ from two threads loses updates
    public synchronized void increment() {
        count++;              // now atomic per lock — no lost updates
    }
    public synchronized int get() { return count; }
}

// Or use an atomic — lock-free, often faster
import java.util.concurrent.atomic.*;
AtomicInteger safe = new AtomicInteger(0);
safe.incrementAndGet();      // thread-safe, no synchronized needed
04

Concurrent Collections

A plain HashMap shared across threads can corrupt itself. The java.util.concurrent package provides thread-safe collections built for exactly this.

Use instead ofThread-safe version
HashMapConcurrentHashMap
ArrayListCopyOnWriteArrayList
a shared queueBlockingQueue (e.g. LinkedBlockingQueue)
a counterAtomicInteger / AtomicLong
The old Collections.synchronizedMap() wraps every method in one big lock, so only one thread touches the map at a time. ConcurrentHashMap locks smaller segments, so many threads can work in parallel — prefer it.
05

Common Pitfalls

Calling run() instead of start()

thread.run() executes on the current thread with no concurrency at all. Only start() creates a new thread.

Race conditions on shared state

Two threads doing count++ without synchronization lose updates, because ++ is read-modify-write, not atomic. Use synchronized or an Atomic type.

Deadlock

Thread A holds lock 1 and waits for lock 2 while thread B holds lock 2 and waits for lock 1 — both block forever. Always acquire multiple locks in the same order.

Prefer higher-level tools

Reach for ExecutorService, ConcurrentHashMap, and Atomic types over raw threads and manual locks. They're correct by construction and far easier to reason about.

✓

Quick Quiz

1. To run code on a new thread you call…
2. An ExecutorService gives you…
3. count++ from two unsynchronized threads can…
4. The thread-safe replacement for HashMap is…
5. Deadlock is avoided by…