Java Multithreading
Thread Lifecycle
| State | Description |
|---|---|
| NEW | Thread created but start() not yet called. |
| RUNNABLE | Thread is ready to run or currently running. |
| BLOCKED | Waiting to acquire a monitor lock. |
| WAITING | Waiting indefinitely for another thread (e.g., wait()). |
| TIMED_WAITING | Waiting for a specified time (e.g., sleep(), join(timeout)). |
| TERMINATED | Thread has finished execution. |
Creating Threads
// Approach 1: extend Thread
class CounterThread extends Thread {
private String name;
public CounterThread(String name) { this.name = name; }
@Override
public void run() {
for (int i = 1; i <= 3; i++) {
System.out.println(name + ": " + i);
try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
}
}
// Approach 2: implement Runnable (preferred — allows extending another class)
class PrintTask implements Runnable {
private String message;
public PrintTask(String message) { this.message = message; }
@Override
public void run() {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + message);
}
}
}
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
// Approach 1
CounterThread t1 = new CounterThread("Thread-A");
CounterThread t2 = new CounterThread("Thread-B");
t1.start();
t2.start();
t1.join(); // wait for t1 to finish
t2.join();
// Approach 2
Thread t3 = new Thread(new PrintTask("Hello"), "Worker-1");
Thread t4 = new Thread(() -> System.out.println("Lambda thread!"), "Worker-2");
t3.start();
t4.start();
}
}
synchronized & wait/notify
import java.util.concurrent.*;
class Counter {
private int count = 0;
// synchronized — only one thread can execute this at a time
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
public class SyncDemo {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// Create 10 threads, each incrementing 1000 times
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) counter.increment();
});
threads[i].start();
}
for (Thread t : threads) t.join();
System.out.println("Final count: " + counter.getCount()); // 10000
// ExecutorService — thread pool (preferred over raw threads)
ExecutorService executor = Executors.newFixedThreadPool(4);
for (int i = 0; i < 8; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("Task " + taskId + " on " + Thread.currentThread().getName());
});
}
executor.shutdown();
executor.awaitTermination(5, TimeUnit.SECONDS);
}
}
Key Takeaways
- A thread is the smallest unit of execution. Java supports multithreading via Thread class and Runnable interface.
- Extend Thread or implement Runnable — prefer Runnable as Java does not support multiple inheritance.
-
Use
synchronizedkeyword to prevent race conditions when multiple threads access shared data. - ExecutorService manages a thread pool — more efficient than creating new threads manually.
- volatile keyword ensures visibility of variable changes across threads — but does not ensure atomicity.
- Use AtomicInteger, AtomicLong for thread-safe counter operations without synchronized blocks.
- Deadlock occurs when two threads wait for each other — always acquire locks in the same order.
Common Mistakes to Avoid
WRONG
new Thread(task).start() in a loop
RIGHT
ExecutorService pool = Executors.newFixedThreadPool(4)
Creating new threads for every task is expensive. Use a thread pool to reuse threads and control concurrency.
WRONG
int count = 0; count++ in multiple threads
RIGHT
AtomicInteger count = new AtomicInteger(0); count.incrementAndGet()
count++ is not atomic — it is read-modify-write. Use AtomicInteger or synchronized for thread-safe counters.
Frequently Asked Questions
Ready to Level Up Your Skills?
Explore 500+ free tutorials across 20+ languages and frameworks.