Introduction
A thread is the smallest unit of execution within a process. Java was designed from the start to support multithreaded programming, and provides language-level keywords (synchronized, volatile), rich library classes (Thread, Runnable, ExecutorService), and a well-defined memory model for concurrent code. Multithreading allows a single Java program to perform several tasks simultaneously — for example, a GUI application can keep its interface responsive while downloading data in the background.
Thread vs Process
A process has its own memory space, file handles, and resources, and is isolated from other processes by the operating system. A thread lives inside a process and shares the process's memory and resources with its sibling threads. Creating a thread is far cheaper than creating a process, and inter-thread communication is faster because threads share memory.
Creating Threads in Java
Java offers two ways to create a thread: extending the Thread class or implementing the Runnable interface. Implementing Runnable is preferred because Java does not support multiple inheritance and a class that extends Thread cannot extend any other class.
// Method 1: Extend Thread
class PrintThread extends Thread {
public void run() {
for (int i=0; i<5; i++) System.out.println("from PrintThread " + i);
}
}
// Method 2: Implement Runnable
class PrintTask implements Runnable {
public void run() {
for (int i=0; i<5; i++) System.out.println("from PrintTask " + i);
}
}
public class Demo {
public static void main(String[] args) {
new PrintThread().start();
new Thread(new PrintTask()).start();
}
}Thread Lifecycle
A Java thread passes through well-defined states: New (created but start() not yet called), Runnable (eligible to run; the OS scheduler decides when it actually gets CPU), Running (currently executing), Blocked / Waiting (waiting for a lock, I/O, or another thread), Timed Waiting (waiting for a fixed duration, e.g. sleep()), and Terminated (run() has returned).
Thread Priorities
Each thread has a priority from Thread.MIN_PRIORITY (1) to Thread.MAX_PRIORITY (10), with Thread.NORM_PRIORITY (5) as default. Priority is a hint to the scheduler and is platform-dependent — never rely on priority for correctness. Set priority using thread.setPriority(7).
Thread Synchronization
When multiple threads share mutable data, their interleaved access can produce incorrect results — a problem called a race condition. Java's synchronized keyword provides mutual exclusion by acquiring an intrinsic lock associated with an object.
class Counter {
private int count = 0;
public synchronized void increment() { count++; }
public synchronized int get() { return count; }
}Without synchronized, two threads calling increment() concurrently could read the same value, both increment it, and both write it back — losing one update. Synchronized methods serialize access so the sequence of reads and writes is atomic.
wait(), notify(), notifyAll()
Threads can coordinate using the wait(), notify(), and notifyAll() methods, which must be called from inside a synchronized block. A thread calls wait() to release the lock and suspend until another thread calls notify() on the same object. This pattern implements the classic producer/consumer problem.
Deadlock
A deadlock occurs when two or more threads each hold a lock that the other needs, and none can proceed. The standard example is thread A holding lock 1 and waiting for lock 2, while thread B holds lock 2 and waits for lock 1. Prevention strategies include acquiring locks in a fixed global order and using tryLock() with a timeout.
Executors and Thread Pools
Modern Java code rarely creates raw Thread objects. The java.util.concurrent package provides ExecutorService and thread pools that manage thread creation and reuse:
ExecutorService pool = Executors.newFixedThreadPool(4);
pool.submit(() -> System.out.println("task"));
pool.shutdown();Summary
Multithreading lets a Java program perform concurrent work. Create threads by extending Thread or (preferably) implementing Runnable. Use synchronized, volatile, and the java.util.concurrent utilities to safely share data between threads. Avoid deadlocks by acquiring locks in a consistent order.
Important Questions
- Define thread and differentiate between thread and process.
- Draw and explain the Java thread lifecycle.
- Compare extending
Threadvs implementingRunnable. - What is a race condition? Show a Java example and fix it using
synchronized. - Explain thread priority in Java with an example.
- What is deadlock? State the Coffman conditions for deadlock.
- Write a producer/consumer solution using
wait/notify. - What is an
ExecutorService? Give an example ofnewFixedThreadPool.