Fundamentals 17 min read

Understanding Java Thread RUNNABLE State and Its Relationship to OS Thread States

This article explains the Java RUNNABLE thread state, how it differs from traditional OS ready and running states, the impact of time‑slicing and I/O blocking, and demonstrates with code that even blocked I/O operations keep a Java thread in the RUNNABLE state.

Selected Java Interview Questions
Selected Java Interview Questions
Selected Java Interview Questions
Understanding Java Thread RUNNABLE State and Its Relationship to OS Thread States

Java virtual‑machine thread states exposed to us differ from the underlying operating‑system thread states; Java thread states are defined by the internal Thread.State enum.

What is RUNNABLE?

According to its Javadoc, a thread in the RUNNABLE state is a thread executing in the Java virtual machine.

A thread executing in the Java virtual machine is in this state.

Traditional process states are shown in the diagram below (note that the term "process" here refers to early single‑threaded processes, effectively meaning thread states).

Note: the "process" here is an early single‑thread process; the so‑called process state is essentially a thread state.

So how does RUNNABLE differ from the "ready" and "running" states shown in the diagram?

Difference from Traditional Ready State

The Javadoc explains:

A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as the processor.

Thus, the RUNNABLE state actually includes the ready state.

It may also include some sub‑states that correspond to the "waiting" state shown in the diagram.

Difference from Traditional Running State

Many think Java thread states lack a separate running state, but that mixes two different layers. In Java, there is no distinct running state; the RUNNABLE state covers it.

Why doesn't the JVM distinguish these two states?

Modern time‑sharing, multi‑task operating systems use a "time‑slice" (time quantum) pre‑emptive round‑robin scheduling.

More complex systems may also incorporate priority mechanisms.

A time slice is usually very small (e.g., 10‑20 ms). After the slice expires, the thread is moved to the end of the scheduling queue, returning to the ready state.

If an I/O operation occurs, the time slice may be released early and the thread enters the waiting queue. Or the slice may be pre‑empted before completion, also returning the thread to the ready state.

This rapid switching is called a context switch; the CPU saves the thread’s execution state to memory for later restoration.

On a single‑core CPU, this creates the illusion of concurrency, similar to a fast‑handed juggler keeping many balls in the air.

The time slice can be configured; a larger slice reduces switching overhead but slows response between threads. On multi‑core CPUs true parallelism is possible, often called "parallel".

Usually Java thread states serve monitoring purposes; if thread switching is extremely fast, distinguishing ready from running has little practical meaning.

When a monitor shows a thread as running, the thread may have already been switched out and back in, causing ready and running states to flash rapidly. Accurate performance evaluation still requires precise running‑time measurement.

Current mainstream JVM implementations map each Java thread to an OS thread and delegate scheduling to the OS; the JVM does not perform its own scheduling, so the JVM‑level state is essentially a mapping/wrapper of the OS state, making a unified RUNNABLE state a reasonable choice.

We will see that Java thread state changes usually only involve mechanisms explicitly introduced by the thread itself.

When I/O Is Blocked

Traditional I/O is blocking because I/O is orders of magnitude slower than the CPU; waiting for I/O would waste CPU time slices.

The solution is to switch the thread out as soon as it reaches I/O code and schedule another ready thread.

The I/O‑blocked thread no longer runs; it is placed in a waiting queue (the "waiting" state in the diagram).

Although the thread is blocked from the CPU’s perspective, other components like the disk continue to work; the CPU and disk operate concurrently.

When I/O completes, an interrupt notifies the CPU (interrupt‑driven I/O), a form of Inversion of Control (IoC): the CPU does not poll the disk but is called back.

The CPU receives an interrupt signal, the current thread is pre‑empted and returns to the ready queue; the previously waiting thread, now with completed I/O, also returns to the ready queue and may be scheduled.

The time‑slice round‑robin itself is driven by a timer interrupt that moves a running thread back to ready.

For example, setting a 10 ms countdown triggers an interrupt when it expires, then resets the countdown in a loop.

The thread currently fighting with the CPU may not like the interrupt, as it means its time slice is over…

Java also defines BLOCKED, WAITING, and TIMED_WAITING states. When performing traditional blocking I/O, which state does the thread have? BLOCKED or WAITING?

The answer is still RUNNABLE. The following test demonstrates this:

@Test
public void testInBlockedIOState() throws InterruptedException {
    Scanner in = new Scanner(System.in);
    // Create a thread named "输入输出"
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // Blocking read from console
                String input = in.nextLine();
                System.out.println(input);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                IOUtils.closeQuietly(in);
            }
        }
    }, "输入输出");
    // Start
    t.start();
    // Ensure run() has started
    Thread.sleep(100);
    // State should be RUNNABLE
    assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE);
}

Adding a breakpoint on the final statement shows the monitor reflecting this state.

Network blocking behaves similarly; for example, socket.accept() is a "blocked" method, yet the thread state remains RUNNABLE.

@Test
public void testBlockedSocketState() throws Exception {
    Thread serverThread = new Thread(new Runnable() {
        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(10086);
                while (true) {
                    // Blocking accept()
                    Socket socket = serverSocket.accept();
                    // TODO
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }, "socket线程");
    serverThread.start();
    // Ensure run() has started
    Thread.sleep(500);
    // State should be RUNNABLE
    assertThat(serverThread.getState()).isEqualTo(Thread.State.RUNNABLE);
}

Monitoring shows the same result.

Java introduced the NIO package long ago; the thread states when using NIO are not covered here.

At least we see that traditional I/O is called "blocking", but this "blocking" is different from the BLOCKED thread state.

How to View the RUNNABLE State?

First, distinguish the two layers:

The virtual machine rides on top of the operating system, which provides resources to satisfy the VM’s needs.

During blocking I/O, the underlying OS thread may indeed be in a blocked state, but we care about the JVM thread state.

The JVM does not care about low‑level details such as when time‑slicing occurs or when I/O triggers a switch.

Recall the Javadoc: a thread in RUNNABLE is executing in the JVM but may be waiting for OS resources such as the processor.

The JVM treats all such resources (CPU, disk, network) as part of execution, so the thread is considered "running".

Whether you use your mouth, hands, or any other tool to satisfy its needs, the JVM does not care.

When a thread is I/O blocked, the CPU stops executing the thread, but the network card or disk may still be active; the CPU and disk operate concurrently.

It’s like a receptionist or security guard staying at their post without visitors; you wouldn’t say they’re not working.

Therefore, the JVM considers the thread still executing.

The OS thread state focuses on the CPU, which differs from the JVM’s perspective.

Mechanisms like synchronized may put a thread into BLOCKED, while sleep , wait , etc., may move it to WAITING.

The correspondence can be visualized as follows:

Thus, the RUNNABLE state corresponds to the traditional ready, running, and part of the waiting states.

Author: Guodong my.oschina.net/goldenshaw/blog/705397

Recent issues:

[Issue 15] Discuss common multithreading interview questions

[Issue 16] Explain how HashMap resolves hash collisions

[Issue 17] When to use ArrayList vs. LinkedList

Instead of searching the web for questions, follow us!

JavaJVMconcurrencyI/OthreadRunnableThread State
Selected Java Interview Questions
Written by

Selected Java Interview Questions

A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.