Skip to content
AgileLogics
6 min read

Concurrency in Depth

Prerequisites: Advanced streams & collectors

Raw Threads don't scale well when a program needs many concurrent tasks — an ExecutorService manages a reusable pool of worker threads instead:

import java.util.concurrent.*;

ExecutorService executor = Executors.newFixedThreadPool(4);

Future<Integer> future = executor.submit(() -> {
    Thread.sleep(100);
    return 42;
});

System.out.println(future.get()); // blocks until the result is ready
executor.shutdown();

When multiple threads share mutable state, synchronized ensures only one thread executes a block at a time:

class Counter {
    private int count = 0;

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

Common mistakes

  • Forgetting to call executor.shutdown() — the pool's threads keep the JVM alive indefinitely otherwise.
  • Sharing a mutable field between threads with no synchronized (or other coordination) at all — leads to a race condition, where the result depends unpredictably on thread timing.