Search⌘ K
AI Features

What Are Threads?

Explore how Java threads allow multiple tasks to run concurrently within a single process by sharing memory and resources. Understand the difference between processes and threads, the main thread's role, and how concurrency differs from parallelism. Learn the thread lifecycle stages and how the JVM and OS manage thread scheduling and context switching for efficient execution.

Up until now, every program we have written has followed a strict, sequential path. We write a line of code, and the computer executes it, then moves to the next. If one line takes five seconds to complete, the entire application halts and waits.

In the real world, however, applications need to do many things at once—a web server handles thousands of requests simultaneously, and a video game renders complex graphics while processing your controller input.

To achieve this in Java, we use threads, independent paths of execution that allow our applications to perform multiple tasks concurrently.

Processes vs. threads

To understand threads, we must first understand the environment they live in: the process.

A process is an instance of a computer program that is being executed. When we start a Java application, the operating system (OS) creates a new process. This process is isolated; it has its own private set of resources and its own allocated memory space that other processes cannot access directly.

A thread (short for “thread of execution”) is a smaller, lightweight unit of execution that lives inside a process. While a process provides the environment and resources, threads are the entities that actually execute the code.

The most critical distinction lies in how they handle memory:

  • Processes are isolated: They do not share memory. If we run two separate Java programs, they cannot access each other’s variables without special communication mechanisms.

  • Threads share memory: All threads within a single process share the process’s heap memory. This allows them to easily access and modify the same objects. However, each thread maintains its own private stack memory to track local variables and method calls.

We can visualize this relationship using a factory analogy. The process is the factory building itself—it has resources, electricity, and a floor plan. The threads are the workers inside the factory. They all work in the same building and share the same tools (heap), but each worker performs their own specific task (execution path) and has their own personal workspace (stack).

Process vs. threads in Java
Process vs. threads in Java

The main thread

Threads are not an optional feature we turn on; they are fundamental to how Java works. Even the simplest “Hello, World!” program runs on a thread.

When the JVM starts, it automatically creates a single thread commonly called the main thread to execute the main() method. Every Java program begins execution on this thread before any additional threads are introduced.

Consider this standard example:

Java 25
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
  • Line 3: Even though we did not explicitly create a thread object, the JVM created the “main” thread behind the scenes to execute this statement.

If our code is strictly sequential, this is the only thread we ever use. However, we can instruct the JVM to spawn additional threads from this main entry point to run tasks in the background. We will see in the next lesson.

Concurrency vs. parallelism

In the world of multitasking, we often hear the terms concurrency and parallelism used interchangeably, but they describe different concepts.

Concurrency is about dealing with multiple things at once. It creates the illusion of simultaneous execution. On a single-core CPU, the computer handles concurrency by rapidly switching between tasks (time-slicing). It executes a bit of Task A, then pauses it to execute a bit of Task B, and so on. To the human eye, it appears they are happening at the same time, but at any given nanosecond, only one task is actually running.

Parallelism is about doing multiple things at once. This requires hardware with multiple processing cores. If we have a multi-core CPU, Task A can run on Core 1 while Task B runs on Core 2 at the exact same instant.

Concurrency vs. parallelism
Concurrency vs. parallelism

Java threads enable concurrency. Whether that concurrency results in actual parallelism depends on the underlying hardware. We write the code to support concurrent execution, and the JVM and OS work together to execute it as efficiently as the hardware allows.

The thread lifecycle

A thread is not just a block of code; it is a dynamic entity that traverses specific states throughout its life. Understanding this lifecycle is vital for debugging and managing complex applications.

  1. New: The thread object has been created, but we have not yet started it. It is just an object in memory, like any other Java object.

  2. Runnable: Once we start the thread, it enters the runnable state. This means the thread is ready to run and is waiting for the OS to assign it to a processor.
    Note: In Java’s formal state model (the Thread.State enum), there is no separate “Running” state. The RUNNABLE state covers both threads that are waiting for the CPU and threads that are currently executing.

  3. Running (conceptual): While Java groups this under RUNNABLE, conceptually, a thread is “running” when it has successfully acquired the CPU and is actively executing code. The OS scheduler moves threads between “waiting for CPU” and “actively using CPU” constantly.

  4. Terminated: The thread has finished its work. This happens when the code it was assigned to run completes execution. Once a thread terminates, it cannot be restarted.

Java thread lifecycle
Java thread lifecycle

JVM scheduling and context switching

Since we often have more threads than CPU cores, not all threads can run at once. The thread scheduler (part of the OS and JVM) is responsible for deciding which thread gets access to the CPU and for how long.

This process involves context switching. To switch from thread A to thread B, the CPU must save the current state of thread A (where it was in the code, the values of its local variables, etc.), load the saved state of thread B, and then resume execution.

This switching happens incredibly fast, but it is not free; it costs CPU cycles. Furthermore, the scheduler is non-deterministic. We cannot guarantee the exact order in which threads will run. If we start two threads at the same time, we do not know which one will print its output first. This unpredictability is a core challenge of concurrent programming, leading to issues like race conditions that we will learn to handle in upcoming lessons.

We have shifted our mental model from a single, sequential line of execution to a world where multiple tasks coexist and share resources. We now understand that threads are lightweight processes that share a process’s memory, and that concurrency allows us to structure programs that handle multiple tasks efficiently. With this conceptual foundation in place, we are ready for the next step: writing the code to create and manage our own threads.