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.