Multithreading Basics: Run Work in Parallel

Introduction

A thread is a lightweight unit of execution within a process. Multithreading helps when tasks can overlap (downloads, background work)—but shared mutable state needs care. This chapter covers Thread, Runnable, join, synchronized intuition, ExecutorService, and InterruptedException (Exceptions). Virtual threads expand on this in Java 21 Features.

Prerequisites

Process vs Thread

  • Process: separate program with its own memory
  • Thread: shares process memory—faster to start, needs synchronization

1) Extend Thread (Less Preferred)

java
class HelloThread extends Thread {
    @Override
    public void run() {
        System.out.println("Hello from " + Thread.currentThread().getName());
    }
}
 
public class ThreadDemo {
    public static void main(String[] args) {
        HelloThread t = new HelloThread();
        t.start();  // not t.run() — run() would stay on main thread
    }
}

2) Implement Runnable (Preferred)

java
Runnable task = () -> System.out.println("Running on " + Thread.currentThread().getName());
Thread t = new Thread(task);
t.start();

Separates task from thread machinery.

3) join — Wait for Thread End

java
Thread worker = new Thread(() -> {
    try {
        Thread.sleep(1000);
        System.out.println("Worker finished");
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
    }
});
 
worker.start();
worker.join();  // main waits for worker
System.out.println("Main continues");

4) Thread Safety and synchronized

When multiple threads mutate shared data:

java
public class Counter {
    private int count = 0;
 
    public synchronized void increment() {
        count++;
    }
 
    public synchronized int getCount() {
        return count;
    }
}

synchronized ensures only one thread executes the method on a given monitor at a time.

Warning

Race conditions are subtle. Prefer immutable data, concurrent collections, or higher-level APIs when possible.

java
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
 
public class ExecutorDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);
 
        pool.submit(() -> System.out.println("Task A"));
        pool.submit(() -> System.out.println("Task B"));
 
        pool.shutdown();
        pool.awaitTermination(5, TimeUnit.SECONDS);
    }
}

Pools reuse threads instead of creating unlimited raw Thread objects.

6) InterruptedException

Thread.sleep, join, and many blocking calls throw checked InterruptedException when another thread interrupts.

java
try {
    Thread.sleep(500);
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();  // restore interrupt flag
    System.out.println("Sleep interrupted");
}

Do not swallow silently—restore interrupt status or exit gracefully.

Common Beginner Mistakes

Calling run() Instead of start()

run() executes on current thread—no new thread.

Sharing Mutable State Without Sync

Counters and collections can corrupt data.

Creating Unbounded Threads

Use thread pools (ExecutorService) for many tasks.

What’s Next

Java 21 Modern Features — virtual threads.

FAQ

When should beginners use threads?

When you understand the problem needs parallelism and you can isolate shared state. Many apps use frameworks that manage threads for you.

Are Java collections thread-safe?

Standard ArrayList / HashMap are not. Use ConcurrentHashMap, CopyOnWriteArrayList, or synchronization.

What is a deadlock?

Two threads wait on each other's locks forever—advanced topic; avoid nested locks on multiple objects.

Virtual threads vs platform threads?

Virtual threads are lightweight; great for many blocking I/O tasks on Java 21+.

Always handle InterruptedException in blocking thread code.