IntermediateReading time: ~12 min

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 handle
  • Runnable — task contract without return value
  • Callable — task contract with return value and exception channel
  • start() — launches a new thread of execution
  • run() — the method body executed by the thread
  • join() — waits for another thread to finish
  • interrupt() — cooperative cancellation signal
  • isInterrupted() — checks interrupt status without clearing it
  • Thread.interrupted() — checks and clears interrupt status
  • daemon thread — background thread that does not keep the JVM alive
  • UncaughtExceptionHandler — hook for unhandled thread failures
  • ThreadLocal — 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:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

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 UncaughtExceptionHandler if 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 execution
  • interrupt() is cooperative cancellation
  • join() 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 not
  • Runnable separates task from thread
  • Callable adds result handling
  • Interrupts are cooperative cancellation, not forced stop
  • Restore the interrupt flag if you catch InterruptedException
  • sleep() does not release locks
  • join() is interruption-sensitive waiting
  • Daemon threads are for best-effort background work only
  • Clean up ThreadLocal in pooled-thread contexts
  • Interjúban nevezd meg a start(), run(), interrupt(), daemon thread és ThreadLocal trade-offjait

🎮 Games

10 questions