Java Concurrency Basics

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 finish

Calling 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 completion

Getting 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 ExecutorService over 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.