HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
5 Topics
Chapter•110 Minutes

Concurrency and Multithreading

Understand threads, executors, synchronization, locks, atomics, visibility, happens-before, concurrent collections, futures, and virtual threads.

Threads and Runnable Tasks

A Java thread represents an execution path. Production applications generally benefit from executors that manage task scheduling and lifecycle.

Runnable task = () ->
    System.out.println(Thread.currentThread().getName());

Thread thread = new Thread(task);
thread.start();

Calling start() creates a new execution path; calling run() directly is an ordinary method call on the current thread.

Shared mutable state is where concurrency becomes difficult. Ask who owns state, who can mutate it, and what synchronization establishes visibility and ordering.

start vs run

start() schedules a new thread. run() is just an ordinary method call when invoked directly.

Key Takeaways
  • Prefer task-based concurrency over many manually managed threads.
  • Thread safety is primarily about shared mutable state.
  • Know the difference between starting a thread and invoking run directly.

synchronized, Locks, and Visibility

synchronized provides mutual exclusion and memory-visibility guarantees. ReentrantLock offers additional features such as timed acquisition.

private final Object lock = new Object();
private int count;

void increment() {
    synchronized (lock) {
        count++;
    }
}

int getCount() {
    synchronized (lock) {
        return count;
    }
}

A data race occurs when concurrent access includes a write without appropriate synchronization. The Java Memory Model defines happens-before relationships.

volatile provides visibility and ordering guarantees but does not make compound operations such as count++ atomic.

Key Takeaways
  • synchronized provides mutual exclusion and visibility.
  • volatile does not make compound read-modify-write operations atomic.
  • The Java Memory Model defines visibility guarantees.

Executors and CompletableFuture

Executors separate task submission from thread management. CompletableFuture composes asynchronous stages.

ExecutorService pool = Executors.newFixedThreadPool(4);

CompletableFuture<String> future =
    CompletableFuture.supplyAsync(() -> loadUser(), pool)
        .thenApply(User::name);

future.thenAccept(System.out::println);

Choose executor sizes based on workload. CPU-bound and blocking I/O workloads usually need different strategies. Always define shutdown behavior for owned executors.

Key Takeaways
  • Executors control concurrency resources.
  • CompletableFuture enables composition of asynchronous operations.
  • Thread-pool sizing depends on workload characteristics.

Concurrent Collections and Atomics

Java provides ConcurrentHashMap, CopyOnWriteArrayList, BlockingQueue, and atomic classes such as AtomicInteger and AtomicReference.

ConcurrentHashMap<String, LongAdder> counters =
    new ConcurrentHashMap<>();

counters.computeIfAbsent("java", key -> new LongAdder())
        .increment();

Concurrent collections provide well-defined concurrent operations. LongAdder can outperform AtomicLong under heavy contention because updates are distributed across cells.

Key Takeaways
  • Use concurrent collections rather than manually synchronizing ordinary collections when appropriate.
  • Atomics support thread-safe single-variable state transitions.
  • Contention characteristics should guide counter selection.

Virtual Threads and Modern Concurrency

Virtual threads are lightweight Java threads designed to make high-concurrency blocking workloads easier to express.

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    Future<String> future =
        executor.submit(() -> fetchRemoteData());
    System.out.println(future.get());
}

Virtual threads do not make CPU-bound work faster. They improve scalability for workloads where many tasks spend time waiting. Concurrency still needs limits around databases, APIs, queues, and other external resources.

Virtual-thread rule

Use virtual threads to scale blocking task concurrency, not as a replacement for CPU parallelism or as permission to remove backpressure.

Key Takeaways
  • Virtual threads are lightweight and suited to many blocking tasks.
  • They do not increase CPU capacity.
  • Concurrency still needs limits around external resources.