Threads
Java
Thread t = new Thread(() -> {
System.out.println("Running in " + Thread.currentThread().getName());
});
t.start(); // start() runs it concurrently
t.join(); // wait for it to finishCalling t.run() directly executes it on the current thread — a classic
mistake that produces no concurrency at all.
ExecutorService
Creating threads by hand doesn't scale. Use a pool:
Java
import java.util.concurrent.*;
try (ExecutorService pool = Executors.newFixedThreadPool(4)) {
for (int i = 1; i <= 10; i++) {
int taskId = i;
pool.submit(() -> {
System.out.println("Task " + taskId);
});
}
} // Java 19+: close() waits for completionGetting results back
Java
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> future = pool.submit(() -> {
Thread.sleep(1000);
return 42;
});
System.out.println(future.get()); // blocks until ready
pool.shutdown();The core problem: shared mutable state
Java
class Counter {
private int count = 0;
public void increment() { count++; } // NOT atomic
}count++ is three operations (read, add, write). Two threads can
interleave and lose an update. Fixes:
Java
// 1. synchronized — one thread at a time
public synchronized void increment() { count++; }
// 2. atomic classes — usually faster
private AtomicInteger count = new AtomicInteger();
public void increment() { count.incrementAndGet(); }Concurrent collections
ArrayList and HashMap are not thread-safe. Use the concurrent
versions instead of wrapping everything in synchronized:
Java
Map<String, Integer> map = new ConcurrentHashMap<>();
List<String> list = new CopyOnWriteArrayList<>();CompletableFuture
For composing asynchronous work:
Java
CompletableFuture.supplyAsync(() -> fetchUser(id))
.thenApply(User::getName)
.thenAccept(System.out::println)
.exceptionally(e -> { log(e); return null; });Practical advice
- Prefer immutable objects — they are automatically thread-safe.
- Prefer
ExecutorServiceover raw threads. - Keep synchronized blocks as small as possible.
- Never assume ordering between threads without explicit synchronisation.
Concurrency bugs are intermittent and hard to reproduce. The most reliable strategy is to share less, not to lock more.