A process is an independent program running in its own memory space. A thread is the smallest unit of execution within a process. Multiple threads share the same process memory, making communication between them fast and efficient.
Benefits of multithreading:
Note: On a single-core CPU, threads are interleaved (concurrency). On multi-core CPUs, threads may truly run in parallel (parallelism).
A Java thread transitions through the following states during its lifetime:
| State | Description |
|---|---|
NEW |
Thread object created but
start() not yet called.
|
RUNNABLE |
Thread is executing or ready to execute; waiting for CPU time. |
BLOCKED |
Waiting to acquire a monitor lock held by another thread. |
WAITING |
Waiting indefinitely for another thread (e.g.,
wait(),
join()).
|
TIMED_WAITING |
Waiting for a specified period (e.g.,
sleep(ms),
wait(ms)).
|
TERMINATED |
Thread has finished execution or was terminated by an exception. |
// Check thread state at runtime
Thread t = new Thread(() -> System.out.println("running"));
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE or TERMINATED
The simplest way to create a thread is to subclass
java.lang.Thread and override the
run() method. Call
start() to launch the new thread.
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
System.out.println(name + " — count: " + i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
MyThread t1 = new MyThread("Thread-A");
MyThread t2 = new MyThread("Thread-B");
t1.start(); // starts a new thread
t2.start(); // starts another new thread
}
}
Limitation: Java supports only single
inheritance, so extending
Thread prevents you from extending
any other class. Prefer
Runnable instead.
The preferred approach is to implement the
Runnable interface and pass it to a
Thread constructor. This separates the
task logic from thread management and allows your class to extend
another class.
public class PrintTask implements Runnable {
private String message;
public PrintTask(String message) {
this.message = message;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(Thread.currentThread().getName()
+ " says: " + message);
}
}
public static void main(String[] args) {
Runnable task1 = new PrintTask("Hello");
Runnable task2 = new PrintTask("World");
Thread t1 = new Thread(task1, "Worker-1");
Thread t2 = new Thread(task2, "Worker-2");
t1.start();
t2.start();
// Lambda shorthand (Java 8+)
Thread t3 = new Thread(() -> System.out.println("Lambda thread!"), "Worker-3");
t3.start();
}
}
This is a classic mistake. Understanding the difference is critical:
| Method | Behavior |
|---|---|
start() |
Creates a new OS thread and schedules
run() on it. Returns
immediately.
|
run() |
Calls the run() method directly
in the current thread. No new thread is created.
|
public class StartVsRun {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("Executing in: "
+ Thread.currentThread().getName());
});
// Correct — executes in a NEW thread
t.start(); // prints "Executing in: Thread-0"
// Wrong — executes in main thread
// t.run(); // prints "Executing in: main"
}
}
Rule: Always call Rule: Always shut down an executor when work is complete.