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
- Exception Handling
- Methods and
static/instancecontext
Process vs Thread
- Process: separate program with its own memory
- Thread: shares process memory—faster to start, needs synchronization
1) Extend Thread (Less Preferred)
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)
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
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:
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.
5) ExecutorService (Recommended for Pools)
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.
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+.
Link to exceptions chapter?
Always handle InterruptedException in blocking thread code.