Skip to main content

Advanced Java

Multithreading

Complete lesson for Multithreading using LEARNCODEGURU’s 6-level learning model — from basics to advanced practice, with Java OOP and Collections where relevant.

L1

Basics — Multithreading

Section: Advanced Java. Threads, Runnable, synchronized, volatile, executors, and ConcurrentHashMap.

What is a thread?

  • A thread is a path of execution inside a process. The JVM starts with one thread: main.
  • Create work with Runnable (or Callable) and pass it to a Thread, or better to an ExecutorService.
  • start() schedules a new thread. Calling run() yourself just runs the code on the *current* thread — a classic interview trap.
public class ThreadBasics {
    public static void main(String[] args) throws InterruptedException {
        Runnable job = () -> System.out.println("hello from " + Thread.currentThread().getName());
        Thread t = new Thread(job, "worker");
        t.start();
        t.join();
        System.out.println("back to " + Thread.currentThread().getName());
    }
}

Thread vs Runnable vs Executor

APIRolePrefer when
extends ThreadThread + job coupledRarely — inflexible
implements RunnableJob onlySimple demos; pass to Thread/executor
CallableJob that returns / throwsNeed a result via Future
ExecutorServiceThread poolAny real application

synchronized and shared data

  • Unsynchronized writes to shared fields race. synchronized methods lock on this (instance) or the Class object (static).
  • synchronized (lock) { } uses an explicit lock object — better than locking this if the instance is public.
  • ConcurrentHashMap is a thread-safe map: concurrent reads/writes without locking the entire map. It does **not** allow null keys or values (unlike HashMap).
L2

Advanced — Multithreading

Visibility, volatile, and the concurrent package

  • volatile guarantees visibility (and ordering) for a single field — not atomic ++. Use AtomicInteger for counters.
  • join() waits for a thread to finish. sleep does not release monitors; wait (on a monitor) does.
  • Prefer java.util.concurrent: Executors.newFixedThreadPool, Future, CompletableFuture (Java 8), ConcurrentHashMap, BlockingQueue.
  • Do not call stop() / suspend() / resume() — they are deprecated and unsafe. Use an interrupt flag or ExecutorService.shutdown().
  • Java 21 virtual threads change scaling, but interviews still start with platform threads, synchronized, and concurrent collections.
L3

Tricky Interview Points

Q1) start() vs run()?

Answer start() creates a new thread that later calls run(). run() invoked directly is a normal method call on the current thread.

Q2) Thread vs Runnable?

Answer Extending Thread wastes your one extends and mixes job with worker. Runnable (or Callable) separates the task; you can run it on any thread or pool.

Q3) What does synchronized do?

Answer It acquires a monitor, so only one thread executes that block for that lock, and it establishes a happens-before so writes become visible.

Q4) HashMap vs ConcurrentHashMap?

Answer HashMap is not thread-safe; concurrent mutation can corrupt it or loop. ConcurrentHashMap allows concurrent access, forbids nulls, and offers atomic compute/merge. Hashtable is the old fully-synchronized map — rarely used now.

Q5) What is volatile?

Answer A visibility/ordering modifier. Reads see the latest write. It does not make i++ atomic.

Q6) How do you wait for a worker to finish?

Answer thread.join(), or future.get(), or CountDownLatch/CyclicBarrier for groups.

Q7) Checked exception on run()?

Answer Runnable.run cannot throw checked exceptions. Use Callable.call or wrap in UncheckedIOException / complete a Future exceptionally.

L4

Simple Programs

ExecutorService and ConcurrentHashMap

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class ConcurrentCount {
    public static void main(String[] args) throws InterruptedException {
        ConcurrentHashMap<String, Integer> counts = new ConcurrentHashMap<>();
        ExecutorService pool = Executors.newFixedThreadPool(4);
        for (int i = 0; i < 100; i++) {
            pool.execute(() -> counts.merge("hits", 1, Integer::sum));
        }
        pool.shutdown();
        pool.awaitTermination(2, TimeUnit.SECONDS);
        System.out.println(counts.get("hits"));
    }
}
L5

Problem Solving Tricks

  • Always shutdown() a pool (try-with-resources works for ExecutorService in Java 19+).
  • Name threads (new Thread(r, "worker-1")) — stack dumps become readable.
  • Do not synchronize on a Boolean or interned String.
  • For a simple flag, volatile boolean running plus interrupt is enough; do not invent a custom lock first.
  • Never use HashMap as a shared cache without a lock — reach for ConcurrentHashMap.
L6

Practice Programs List

  1. Start two threads that print 1..5 and join both from main.
  2. Show a race on int counter++ without synchronized, then fix it with AtomicInteger.
  3. Replace extends Thread with a lambda Runnable on an executor.
  4. Put 10,000 merge calls on a ConcurrentHashMap and check the total.
  5. Demonstrate that calling run() instead of start() keeps you on main.