What is a Java Thread?
Learn what a Java thread is, how start() differs from run(), Runnable vs Callable, and how synchronization prevents race conditions in concurrent code.
Expected Interview Answer
A thread is the smallest unit of execution in Java, an independent path of control that runs concurrently with other threads inside the same process, sharing heap memory but each with its own stack, program counter, and call frame.
You create a thread by extending Thread or, more commonly, by implementing Runnable (or Callable for a return value) and handing it to a Thread or an ExecutorService. Calling start() schedules the thread for execution by the JVM/OS scheduler, while calling run() directly just executes the code on the current thread with no new thread created. Because threads share the heap, concurrent access to mutable shared state requires synchronization (synchronized blocks, locks, or concurrent collections) to avoid race conditions, and the JVM's memory model dictates when writes by one thread become visible to another. Modern code typically avoids managing raw Thread objects directly, preferring the ExecutorService thread pool abstraction for better resource management.
- Enables true concurrent execution to use multi-core CPUs
- Improves throughput for I/O-bound and parallelizable work
- Runnable/Callable decouple task logic from thread management
- ExecutorService pools threads for efficient reuse and control
- Synchronization primitives keep shared state consistent
AI Mentor Explanation
A thread is like one specific fielder running their own individual path on the ground, independent of every other fielder yet sharing the same pitch and match state as everyone else. Calling start() is like the umpire actually signaling that fielder to move, while just describing the plan on paper without a signal never gets anyone running.
Thread lifecycle in the JVM
New
- Thread object created, start() not yet called
Runnable
- start() called, waiting for CPU scheduling
Running
- Scheduler picked it, executing run()
Blocked/Waiting
- Waiting on a lock, join(), or wait()
Terminated
- run() completed or thread was stopped
Step-by-Step Explanation
Step 1
Define the task
Implement Runnable (no return value) or Callable<V> (returns a value, can throw checked exceptions).
Step 2
Wrap it in a Thread or submit to an ExecutorService
new Thread(runnable) for a manual thread, or executorService.submit(task) for pooled execution.
Step 3
Call start(), never run() directly
start() schedules a new OS-backed thread; calling run() directly just executes synchronously on the current thread.
Step 4
Protect shared mutable state
Use synchronized, java.util.concurrent.locks, or concurrent collections to prevent race conditions on shared heap data.
Step 5
Coordinate completion
Use join() to wait for a thread to finish, or Future.get() with ExecutorService to retrieve a Callable's result.
Step 6
Shut down thread pools
Call executorService.shutdown() to release pooled threads once work is done, avoiding resource leaks.
What Interviewer Expects
- Knows the difference between start() and calling run() directly
- Understands threads share heap but have their own stack
- Knows Runnable vs Callable and when to use each
- Can explain why synchronization is needed for shared mutable state
- Prefers ExecutorService over manually managing raw Thread objects
Common Mistakes
- Calling run() instead of start(), which silently skips actual concurrency
- Assuming threads have separate heaps rather than a separate stack only
- Forgetting to synchronize access to shared mutable state, causing race conditions
- Not shutting down an ExecutorService, leaking threads
- Confusing thread-safety with atomicity of individual operations like i++
Best Answer (HR Friendly)
“A thread is an independent path of execution within a program that lets multiple things happen at seemingly the same time, sharing the same underlying data. Java lets developers create threads directly or, more commonly today, use thread pools to run tasks concurrently, while using careful coordination to avoid two threads corrupting shared data at once.”
Code Example
import java.util.concurrent.*;
public class Counter {
private int count = 0;
public synchronized void increment() { // protects shared state
count++;
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 1000; i++) {
pool.submit(counter::increment); // Runnable via method reference
}
pool.shutdown();
pool.awaitTermination(5, TimeUnit.SECONDS);
System.out.println(counter.count); // 1000, thanks to synchronized
}
}Follow-up Questions
- What is the difference between Runnable and Callable?
- What happens if you call run() instead of start() on a Thread?
- How does the synchronized keyword prevent race conditions?
- What is the difference between wait/notify and java.util.concurrent locks?
- Why prefer ExecutorService over manually creating Thread objects?
MCQ Practice
1. What does calling thread.start() actually do?
start() creates and schedules a genuinely new thread; only calling run() directly would execute synchronously without concurrency.
2. What is shared between threads in the same process?
Threads in the same process share heap memory (objects), while each thread has its own stack and program counter.
3. Which interface should you use if your concurrent task needs to return a value?
Callable<V> can return a value and throw checked exceptions, unlike Runnable which returns nothing.
Flash Cards
What does start() do that run() does not? — start() schedules a genuinely new OS thread; run() called directly just executes on the current thread with no concurrency.
What do threads share vs keep private? — They share heap memory; each thread has its own stack and program counter.
Runnable vs Callable? — Runnable has no return value and can't throw checked exceptions; Callable<V> returns a value and can throw checked exceptions.
Why use ExecutorService over raw Thread objects? — It pools and reuses threads, manages lifecycle, and avoids the overhead/risk of manual thread management.