Thread Basics
Thread vs Runnable, lifecycle and daemon threads
Thread basics in Java means understanding how work is attached to a thread, how that thread lives and stops, and how interruption, daemon behavior, and thread ownership affect correctness in production. Interviews use this topic to separate syntax knowledge from real concurrency reasoning.
1. Definition
What is a thread?
A Java thread is an independent path of execution managed by the JVM.
In most common deployments it is backed by an operating-system thread.
That means a thread is not just a Java object.
The Thread instance is the Java-side handle.
The real runtime behavior also depends on:
- the JVM scheduler interaction
- the operating system scheduler
- blocking I/O
- lock contention
- the Java Memory Model
- safepoints and GC interaction
Concurrency versus parallelism
Concurrency means multiple units of work can make progress independently.
Parallelism means they actually run at the same time on different cores.
A server can be highly concurrent even if little real parallel work happens, for example when many threads are blocked on I/O.
That distinction matters in interviews.
A strong answer says:
- concurrency is about structure and coordination
- parallelism is about simultaneous execution
Why this topic matters
Once work is attached to a raw thread, lifecycle becomes an operational concern.
You now care about:
- startup policy
- cancellation policy
- failure handling
- naming
- cleanup
- shutdown guarantees
That is why thread basics are bigger than calling start().
2. Core Concepts
2.1 `Thread` versus `Runnable` versus `Callable`
Extending Thread couples the task and the execution mechanism.
Implementing Runnable separates the work from the thread that runs it.
Callable adds a result and checked exception channel.
In real systems this is why tasks are usually expressed as Runnable or Callable, while executors decide how they run.
2.1.1 Keywords and contracts you should state explicitly here
This topic becomes much clearer if you explicitly name the core contracts:
Thread— JVM-level execution handleRunnable— task contract without return valueCallable— task contract with return value and exception channelstart()— launches a new thread of executionrun()— the method body executed by the threadjoin()— waits for another thread to finishinterrupt()— cooperative cancellation signalisInterrupted()— checks interrupt status without clearing itThread.interrupted()— checks and clears interrupt status- daemon thread — background thread that does not keep the JVM alive
UncaughtExceptionHandler— hook for unhandled thread failuresThreadLocal— per-thread state storage- thread dump — diagnostic snapshot of thread stacks and states
These words are interview keywords and production debugging keywords at the same time.
2.2 Lifecycle and states
Java exposes thread states such as:
NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED
RUNNABLE does not necessarily mean the thread is currently on CPU.
It means the thread is eligible to run.
It may still be waiting for scheduling.
BLOCKED often means waiting to enter a synchronized monitor.
WAITING and TIMED_WAITING appear with APIs such as:
Object.wait()Thread.sleep()Thread.join()LockSupport.park()
2.3 Interrupts as cancellation
Interrupts are Java’s cooperative cancellation mechanism.
Calling interrupt() does not kill a thread forcibly.
It sets an interrupt flag.
Certain blocking APIs react by throwing InterruptedException.
Correct code either:
- propagates the interruption
- or restores the flag with
Thread.currentThread().interrupt()
Swallowing interruption is one of the classic concurrency bugs.
2.4 Daemon threads
Daemon threads do not keep the JVM alive.
If only daemon threads remain, the JVM may exit.
That means they are suitable for best-effort background helpers.
They are dangerous for work that must complete reliably.
3. Practical Usage
When raw threads are acceptable
Raw threads are acceptable when:
- you are teaching the fundamentals
- you need very explicit ownership
- you are integrating with a low-level API
- the background worker is narrow and well-bounded
Even then, you should still make several choices explicit:
- give the thread a meaningful name
- decide whether it is daemon or non-daemon
- attach an
UncaughtExceptionHandlerif the work is long-lived - define how it stops
Cooperative stop logic
Long-running loops should not assume they run forever.
They should periodically check interruption.
Blocking code should use interruption-friendly APIs where possible.
A good thread loop is usually:
- work while not interrupted
- react quickly to interruption
- close resources
- return cleanly
Thread naming and diagnostics
Thread names are not cosmetic.
They appear in:
- thread dumps
- profiler output
- log correlation
- monitoring dashboards
Thread-47 is much less useful than invoice-writer-1.
`ThreadLocal` use
ThreadLocal can be useful for:
- request correlation
- per-thread formatting helpers
- narrowly scoped caches
But in pooled threads it can leak state from one logical request to another if you forget remove().
4. Code Examples
Example 1: Proper thread startup and interruption
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
System.out.println("processing batch");
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("worker stopped cleanly");
}, "inventory-sync-1");
worker.setUncaughtExceptionHandler((thread, ex) ->
System.err.println(thread.getName() + " failed: " + ex.getMessage()));
worker.start();
worker.interrupt();
worker.join();
Key points:
start()creates concurrent executioninterrupt()is cooperative cancellationjoin()waits for a clean stop- the catch block restores the interrupt flag
Example 2: A daemon helper with explicit stop
class HeartbeatService {
private final AtomicBoolean running = new AtomicBoolean();
private Thread thread;
void start() {
if (!running.compareAndSet(false, true)) {
return;
}
thread = new Thread(this::runLoop, "heartbeat-daemon");
thread.setDaemon(true);
thread.start();
}
private void runLoop() {
while (running.get() && !Thread.currentThread().isInterrupted()) {
try {
sendHeartbeat();
Thread.sleep(1_000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
void stop() throws InterruptedException {
running.set(false);
if (thread != null) {
thread.interrupt();
thread.join(2_000);
}
}
private void sendHeartbeat() {}
}
Key points:
- daemon is not a replacement for explicit stop
- lifecycle still matters
- interruption remains part of shutdown
Example 3: `ThreadLocal` cleanup
class RequestContextHolder {
private static final ThreadLocal<String> REQUEST_ID = new ThreadLocal<>();
static void handle(String requestId) {
REQUEST_ID.set(requestId);
try {
process();
} finally {
REQUEST_ID.remove();
}
}
private static void process() {}
}
Key point:
- pooled threads require explicit cleanup of thread-local state
5. Trade-offs
| Choice | Advantage | Cost or risk |
|---|---|---|
Raw Thread |
Explicit ownership and low-level clarity | Harder lifecycle, scaling, and diagnostics |
Runnable |
Separates task from execution mechanism | No result channel |
Callable |
Adds result and exception channel | Usually needs an executor to be useful |
| Daemon thread | Good for best-effort helpers | Unsafe for critical completion guarantees |
ThreadLocal |
Convenient per-thread context | Easy state leakage with pooled threads |
Practical trade-off analysis
Raw threads give control.
But they do not give:
- pooling
- admission control
- structured shutdown across many tasks
- automatic result propagation
That is why thread basics matter, but executors usually become the default next step.
The senior rule is:
- use raw threads for clear ownership boundaries
- do not use them as your general task-dispatch strategy
6. Common Mistakes
Mistake 1: Calling `run()` instead of `start()`
Calling run() directly executes on the caller thread.
Correct approach:
- call
start()when you want actual concurrent execution
Mistake 2: Swallowing `InterruptedException`
Logging and continuing often destroys shutdown behavior.
Correct approach:
- propagate the exception or restore the flag
Mistake 3: Assuming `sleep()` releases locks
It does not.
A sleeping thread inside a synchronized block still holds the monitor.
Correct approach:
- do not sleep inside critical sections unless you understand the consequence
Mistake 4: Misusing daemon threads
Teams sometimes mark a thread daemon only to hide shutdown bugs.
Correct approach:
- fix lifecycle ownership instead of masking it with daemon semantics
Mistake 5: Forgetting `ThreadLocal.remove()`
This causes state leakage and sometimes memory retention.
Correct approach:
- remove thread-local values in
finally
Mistake 6: Uncontrolled thread creation
Creating threads in a loop under load often causes scheduler thrashing and memory pressure.
Correct approach:
- use an executor when task count can grow dynamically
7. Deep Dive
7.1 Why thread state is a diagnostic tool
Thread states are not just theory.
They help you read thread dumps.
Many threads in BLOCKED suggest monitor contention.
Many in TIMED_WAITING can suggest:
- sleep-based polling
- backoff logic
- idle pools
Many in RUNNABLE with high CPU can suggest:
- spin loops
- CPU-heavy work
- native blocking that still appears runnable
7.2 Interrupt policy is part of API design
A thread does not just need work logic.
It needs a cancellation contract.
That contract should answer:
- how does the loop stop?
- how quickly does blocking code react?
- what cleanup runs on interruption?
- does the caller observe the cancellation?
That is why interruption is not a minor detail.
It is part of the component contract.
7.3 Daemon semantics and reliability
Daemon threads are useful for:
- metrics helpers
- best-effort housekeeping
- optional background refreshers
They are a bad fit for:
- critical writes
- required shutdown flushing
- business workflows that must complete
7.4 Raw threads versus modern Java
Modern Java adds virtual threads.
But thread basics still matter.
You still need to understand:
- interruption
- naming
- cancellation
- ownership
- failure handling
The abstractions evolve, but the invariants remain.
8. Interview Questions
1. What is the difference between `start()` and `run()`?
start() launches a new thread of execution.
run() is just a normal method call if invoked directly.
2. What is the difference between concurrency and parallelism?
Concurrency is about independently progressing tasks.
Parallelism is about simultaneous execution on multiple cores.
3. What does interruption do?
It signals cooperative cancellation.
It does not forcibly kill the thread.
4. Why is swallowing `InterruptedException` dangerous?
Because it breaks cancellation and shutdown coordination.
5. What is a daemon thread?
A background thread that does not keep the JVM alive.
6. Why is `ThreadLocal` risky with pools?
Because physical worker threads get reused across logical requests.
7. Does `sleep()` release a monitor lock?
No.
8. Why should threads have names?
Because names improve diagnostics in dumps, logs, and profilers.
9. When is a raw thread acceptable?
When ownership is narrow and explicit, or when teaching low-level fundamentals.
10. What is the senior-level takeaway?
Thread creation is easy.
Thread lifecycle design is the real engineering problem.
9. Glossary
| Term | Meaning |
|---|---|
Thread |
JVM-managed execution path |
Runnable |
Task contract without return value |
Callable |
Task contract with result and exception channel |
interrupt() |
Cooperative cancellation signal |
join() |
Wait for another thread to finish |
| daemon thread | Thread that does not keep the JVM alive |
ThreadLocal |
Per-thread state storage |
| thread dump | Diagnostic snapshot of thread stacks and states |
UncaughtExceptionHandler |
Handler for unhandled thread failure |
isInterrupted() |
Checks interrupt flag without clearing it |
10. Cheatsheet
start()creates concurrency;run()alone does notRunnableseparates task from threadCallableadds result handling- Interrupts are cooperative cancellation, not forced stop
- Restore the interrupt flag if you catch
InterruptedException sleep()does not release locksjoin()is interruption-sensitive waiting- Daemon threads are for best-effort background work only
- Clean up
ThreadLocalin pooled-thread contexts - Interjúban nevezd meg a
start(),run(),interrupt(), daemon thread ésThreadLocaltrade-offjait
🎮 Games
10 questions