Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,104visitors

Built for engineers who want to go deep.


← Java Concurrency & Multithreading

Threads & Runnable

  • Introduction to Threads
  • ExecutorService & Thread Pools
  • Fork/Join Framework

Synchronization & Memory Model

  • synchronized and Locks
  • volatile and the Memory Model
  • CompletableFuture
  • Deadlock, Starvation, Livelock

Concurrent Collections

  • ConcurrentHashMap & CopyOnWriteArrayList
  • HashMap Concurrency Variants
  • Concurrent Utilities & Coordination
Chaturmind
← Java Concurrency & Multithreading

Threads & Runnable

  • Introduction to Threads
  • ExecutorService & Thread Pools
  • Fork/Join Framework

Synchronization & Memory Model

  • synchronized and Locks
  • volatile and the Memory Model
  • CompletableFuture
  • Deadlock, Starvation, Livelock

Concurrent Collections

  • ConcurrentHashMap & CopyOnWriteArrayList
  • HashMap Concurrency Variants
  • Concurrent Utilities & Coordination
HomeLearnJavaJava Concurrency & MultithreadingThreads & Runnable
✓ FreeIntermediate· 6 min read

Introduction to Threads

Create and start threads — Thread class, Runnable, and the lifecycle of a thread.

Published September 21, 2026


Introduction to Threads

A thread is an independent path of execution inside a program. Every Java program starts with one, the main thread, and can create more. All threads in a process share the same heap memory (objects, static fields), but each has its own stack (local variables and method calls).

Why use more than one?

  • Responsiveness: a UI or server keeps responding while slow work (file I/O, network calls) happens elsewhere.
  • Throughput: a server handles many requests concurrently, overlapping one request's waiting with another's work.
  • Parallelism: CPU-heavy work split across cores finishes faster.

The price is that shared memory, accessed by several threads at once, introduces bugs that don't exist in single-threaded code. Most of the Java concurrency course is about managing that price.

Advertisement

Creating and starting threads

// 1. A Runnable (preferred: the task is separate from the thread)
Runnable task = () -> System.out.println("Running in " + Thread.currentThread().getName());
Thread t1 = new Thread(task, "worker-1");
t1.start();

// 2. Subclassing Thread (works, but mixes "what to do" with "the thread itself")
class Downloader extends Thread {
    @Override public void run() { download(); }
}
new Downloader().start();

// 3. Java 21 builders — platform or virtual
Thread t2 = Thread.ofPlatform().name("indexer").start(task);
Thread t3 = Thread.ofVirtual().start(task);

start() vs run(): start() creates a new thread, which then calls run(). Calling run() directly just executes the method on the current thread, with no concurrency. This is a common beginner mistake. Calling start() twice on the same thread throws IllegalThreadStateException.

In real applications, you rarely create threads by hand. You submit tasks to an ExecutorService (see ExecutorService & Thread Pools), or use virtual threads for I/O-heavy work (see Virtual Threads).

Thread lifecycle

NEW ──start()──▶ RUNNABLE ◀────────────────────────────┐
                    │                                   │
      waiting for a monitor lock ──▶ BLOCKED ───────────┤
      wait() / join() / park()   ──▶ WAITING ───────────┤
      sleep(ms) / wait(ms) / join(ms) ▶ TIMED_WAITING ──┘
                    │
              run() returns / throws ──▶ TERMINATED

RUNNABLE means "eligible to run". Whether it's actually on a CPU right now is up to the OS scheduler. You can inspect a thread's state with getState(), and thread dumps (jstack, or jcmd <pid> Thread.print) show every thread's state, which is essential for diagnosing hangs.

Coordinating threads

Thread worker = new Thread(() -> processBatch());
worker.start();
worker.join();                 // wait for it to finish (join(ms) waits with a timeout)

Thread.sleep(500);             // pause the current thread; throws InterruptedException

Interruption: the cooperative way to stop a thread

Java has no safe way to forcibly kill a thread (Thread.stop() was deprecated because it could leave objects half-updated). Instead, you ask a thread to stop with interrupt(), and the thread checks for it:

Thread poller = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        try {
            pollQueue();
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();   // restore the flag, then exit the loop
        }
    }
});
poller.start();
// later
poller.interrupt();

Blocking methods (sleep, wait, join, BlockingQueue.take) respond to interruption by throwing InterruptedException, which clears the interrupt flag. Never swallow it silently. Either propagate it, or restore the flag with Thread.currentThread().interrupt(), so the code above you can see it.

Daemon threads

Thread t = new Thread(task);
t.setDaemon(true);   // must be set before start()

The JVM exits when only daemon threads remain. Use daemon threads for background housekeeping that shouldn't keep the application alive. They can be stopped abruptly at exit, so don't use them for work that must complete, such as writing files.

The core problem: shared mutable state

class Counter {
    private int count = 0;
    void increment() { count++; }     // NOT atomic: read, add, write
    int get() { return count; }
}

Counter c = new Counter();
Thread a = new Thread(() -> { for (int i = 0; i < 100_000; i++) c.increment(); });
Thread b = new Thread(() -> { for (int i = 0; i < 100_000; i++) c.increment(); });
a.start(); b.start();
a.join();  b.join();
System.out.println(c.get());   // usually LESS than 200000

count++ is three steps. Both threads can read 41, both write 42, and one increment is lost. This is a race condition. Two related problems:

  • Visibility: without synchronization, a thread might not see another thread's writes (values may stay in CPU caches or registers). See volatile and the Memory Model.
  • Atomicity: compound actions (check-then-act, read-modify-write) must execute as a unit.

The fixes, covered in the following lessons:

  • synchronized or Lock for mutual exclusion (see synchronized and Locks).
  • Atomic classes (AtomicInteger.incrementAndGet()).
  • Concurrent collections (ConcurrentHashMap).
  • The best fix of all: avoid sharing mutable state, using immutable objects, or confining data to one thread.

Useful thread facts

  • Give threads names. Thread dumps full of Thread-37 are hard to debug.
  • Uncaught exceptions end only the thread that threw them, and print a stack trace. Set a handler (Thread.setDefaultUncaughtExceptionHandler) to log them properly.
  • Priorities (setPriority) are just hints that the OS may ignore. Don't rely on them for correctness.
  • Thread.yield() is a scheduler hint, and almost never needed.

Follow-up questions this topic invites — and their answers

Q: What's the difference between a process and a thread? A: A process has its own memory space, isolated from other processes. Threads live inside a process and share its heap, which makes communication cheap but requires synchronization. Each thread has its own stack and program counter.

Q: What happens if you call run() instead of start()? A: The method executes synchronously on the calling thread, like any normal method call. No new thread is created, so there's no concurrency.

Q: How do you stop a thread safely? A: Call interrupt(), and write the thread's code to check isInterrupted() and to handle InterruptedException by cleaning up and exiting. For executor tasks, cancel the Future or shut down the executor, which interrupts running tasks.

Q: Why is count++ not thread-safe? A: It's a read, an increment and a write. Two threads can interleave those steps, and both write the same value, losing an update. Use AtomicInteger, LongAdder, or synchronization.

Q: What's the difference between BLOCKED and WAITING? A: BLOCKED means the thread is waiting to acquire a monitor lock (entering a synchronized block). WAITING means it's waiting indefinitely for another thread to act: wait(), join(), or LockSupport.park(), which also covers waits on java.util.concurrent locks.

Advertisement

Next

ExecutorService & Thread Pools

AI Tutor

Lesson: Introduction to Threads

Quick actions

AI responses can be inaccurate. Verify critical information.