Fundamentals 19 min read

Understanding Java Threads, Thread Models, and Scheduling

This article explains what threads are, how Java maps its threads to operating‑system thread models (1:1, N:1, N:M), the differences between kernel and user threads, thread scheduling, context switching, priority handling, and includes sample Java code to compare serial and multithreaded execution.

政采云技术
政采云技术
政采云技术
Understanding Java Threads, Thread Models, and Scheduling

What Is a Thread

Modern operating systems create a process for each program; a Java program runs in a Java process that can contain multiple threads, each with its own counter, stack, and local variables. Threads are lightweight scheduling units that share memory and I/O, allowing the CPU to switch rapidly between them.

Thread Implementation

Operating systems provide thread implementations, and Java abstracts these across platforms. Each started java.lang.Thread instance represents a thread whose key methods are declared native, indicating low‑level OS interaction.

There are three main thread implementation models: kernel‑level (1:1), user‑level (N:1), and hybrid user‑level with lightweight processes (N:M).

1. Kernel‑Level Threads (1:1)

Kernel‑Level Threads (KLT) are directly supported by the OS scheduler; each KLT corresponds to a Light‑Weight Process (LWP). This one‑to‑one relationship makes each thread an independent scheduling unit, but incurs system‑call overhead and consumes kernel resources.

Illustration of the 1:1 relationship between lightweight processes and kernel threads.

2. User‑Level Threads (N:1)

User threads exist entirely in user‑space; the kernel is unaware of them. All user threads map to a single kernel thread, allowing fast creation and scheduling without kernel involvement, but a blocked thread can stall the entire process.

Illustration of the N:1 relationship between processes and user threads.

3. Hybrid User‑Level + LWP (N:M)

This model combines user threads with lightweight processes. User threads remain in user space, while LWPs act as bridges to kernel threads, enabling many user threads with kernel‑level scheduling and reducing the risk of whole‑process blocking.

Illustration of the N:M relationship between user threads and lightweight processes.

Java Thread Implementation

The OS thread model influences how the JVM maps Java threads to native threads, though the JVM specification does not mandate a specific model. Java developers generally do not need to consider these details.

Before JDK 1.3, Java used green (user‑level) threads; since then it has adopted a 1:1 model on most platforms, with HotSpot VM delegating scheduling entirely to the underlying OS.

Java Thread Scheduling

Thread scheduling allocates CPU time to threads. Java uses pre‑emptive scheduling, where the OS decides when to switch threads, typically based on time slices.

Time slices are short CPU intervals (often tens of milliseconds) that give the illusion of simultaneous execution. When a slice expires or a thread blocks, the OS performs a context switch.

Context Switching

A context switch saves a thread's execution state (registers, program counter, etc.) so it can resume later. Switching can be triggered by time‑slice exhaustion, higher‑priority pre‑emption, blocking operations, synchronization, or interrupts.

Context switches may be self‑initiated (voluntary) or forced by the system (involuntary). Frequent switches, especially across multiple CPUs, can be costly.

Thread Priority

Java allows suggesting thread priority via setPriority() . Higher‑priority threads are more likely to receive CPU time, but actual scheduling depends on the underlying OS, which may have fewer priority levels than Java.

On Windows, many Java priority levels map to the same OS level, and the OS may apply a "priority booster" that can override Java's hints.

Summary

Understanding threads, their OS implementations, scheduling, and priority handling is essential for writing efficient multithreaded Java programs. The article also provides a code example comparing serial and multithreaded execution, demonstrating the overhead of context switching.

Code Example: Listing Threads

public class thread {
  public static void main(String[] args) {
    // 获取Java线程管理
    ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    // 仅获取线程和线程堆栈信息
    ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
    // 遍历线程信息,仅仅打印线程 ID 和线程名称信息
    for (ThreadInfo threadInfo : threadInfos) {
      System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
    }
  }
}

Code Example: Serial vs. Multithreaded Execution

package com.yuyy.test;

public class DemoApplication {
  public static void main(String[] args) {
    // 运行多线程
    MultiThreadTester test1 = new MultiThreadTester();
    test1.Start();
    // 运行单线程
    SerialTester test2 = new SerialTester();
    test2.Start();
  }

  static class MultiThreadTester extends ThreadContextSwitchTester {
    @Override
    public void Start() {
      long start = System.currentTimeMillis();
      MyRunnable myRunnable1 = new MyRunnable();
      Thread[] threads = new Thread[4];
      // 创建多个线程
      for (int i = 0; i < 4; i++) {
        threads[i] = new Thread(myRunnable1);
        threads[i].start();
      }
      for (int i = 0; i < 4; i++) {
        try {
          // 等待一起运行完
          threads[i].join();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      long end = System.currentTimeMillis();
      System.out.println("multi thread exec time: " + (end - start) + "s");
      System.out.println("counter: " + counter);
    }
    // 创建一个实现Runnable的类
    class MyRunnable implements Runnable {
      public void run() {
        while (counter < 100000000) {
          synchronized (this) {
            if (counter < 100000000) {
              increaseCounter();
            }
          }
        }
      }
    }
  }

  static class SerialTester extends ThreadContextSwitchTester {
    @Override
    public void Start() {
      long start = System.currentTimeMillis();
      for (long i = 0; i < count; i++) {
        increaseCounter();
      }
      long end = System.currentTimeMillis();
      System.out.println("serial exec time: " + (end - start) + "s");
      System.out.println("counter: " + counter);
    }
  }

  static abstract class ThreadContextSwitchTester {
    public static final int count = 100000000;
    public volatile int counter = 0;
    public void increaseCounter() {
      this.counter += 1;
    }
    public abstract void Start();
  }
}

References

Deep Understanding of the Java Virtual Machine (3rd Edition)

JavaconcurrencymultithreadingOperating SystemThread Schedulingthreads
政采云技术
Written by

政采云技术

ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining 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.