Skip to content

什么是线程上下文切换?

通常有四种情况会发生线程上下文切换。 第一种是时间片耗尽,操作系统为每个线程分配了一个时间片,当线程的时间片用完后,操作系统会强制切换到其他线程,这是为了保证多个线程能够公平地共享 CPU 资源。 第二种是线程主动让出 CPU,当线程调用了某些方法,如 Thread.sleep()、Object.wait() 或 LockSupport.park()等,会使线程主动让出 CPU,导致上下文切换。 第三种是调用了阻塞类型的系统中断,比如:线程执行 I/O 操作时,由于 I/O 操作通常需要等待外部资源,线程会被挂起,会触发上下文切换。 第四种是被终止或结束运行。 线程上下文切换的过程,分为四步。 第一步是保存当前线程的上下文,将当前线程的寄存器状态、程序计数器、栈信息等保存到内存中。 第二步是根据线程调度算法,如:时间片轮转、优先级调度等,选择下一个要运行的线程。 第三步是加载下一个线程的上下文,从内存中恢复所选线程的寄存器状态、程序计数器和栈信息。 第四步是 CPU 开始执行被加载的线程的代码。

定义

  • 线程上下文切换(Thread Context Switch)
  • 是操作系统在多线程执行时,将 CPU 从一个线程切换到另一个线程的过程,涉及保存当前线程的状态并加载新线程的状态。

核心点

  • 目的:实现多线程并发。
  • 代价:切换涉及时间和资源开销。

1. 线程上下文切换详解

(1) 基本概念

  • 上下文
  • 线程运行时的状态,包括:
    • 寄存器:如程序计数器(PC)、栈指针。
    • 线程栈:局部变量、调用栈。
    • 状态:如运行、等待。
  • 切换过程
  • CPU 暂停当前线程,保存上下文到内存(PCB,进程控制块或线程控制块)。
  • 加载新线程的上下文到 CPU,继续执行。

(2) 触发场景

  • 时间片用尽
  • 多线程抢占式调度,时间片到期。
  • 线程阻塞
  • 如等待 I/O、锁、调用 sleep()
  • 线程优先级
  • 高优先级线程抢占。
  • 手动让出
  • 调用 Thread.yield()

(3) 执行步骤

  1. 保存当前线程
  2. 寄存器值(如 PC、SP)存入线程的栈或 PCB。
  3. 调度新线程
  4. 操作系统选择就绪队列中的线程。
  5. 加载新线程
  6. 将新线程的上下文恢复到寄存器。
  7. 切换执行
  8. CPU 开始执行新线程代码。

2. 开销与影响

(1) 时间开销

  • 直接开销
  • 保存和加载上下文,约几微秒到几十微秒(视硬件和 OS)。
  • 间接开销
  • 缓存失效(CPU Cache 未命中)。
  • TLB(翻译后备缓冲器)刷新。

(2) 系统影响

  • 性能
  • 频繁切换降低吞吐量。
  • 响应性
  • 适度切换提升多任务并行。

示例

  • 线程 A 执行中,调用 sleep(1000)
  • A 的上下文保存。
  • 切换到线程 B,B 执行。
  • 1 秒后,A 恢复。

3. Java 中的体现

(1) JVM 与 OS

  • Java 线程由 JVM 管理,映射到操作系统原生线程。
  • 上下文切换由 OS 内核完成,JVM 无直接控制。

(2) 相关方法

  • Thread.sleep()
  • 触发切换,进入 Timed Waiting。
  • Thread.yield()
  • 让出 CPU,回到 Runnable。
  • wait()
  • 等待时切换至其他线程。

(3) 查看切换

  • 工具
  • JVisualVM、Thread Dump 分析线程状态。
  • 指标
  • 上下文切换次数(Linux:vmstatcs 列)。

4. 延伸与面试角度

  • 与进程切换对比
  • 线程切换:同一进程内,共享内存,较轻量。
  • 进程切换:不同进程,切换地址空间,开销大。
  • 优化策略
  • 减少线程数:用线程池。
  • 避免频繁阻塞:异步 I/O。
  • 调整优先级:减少抢占。
  • 实际应用
  • 高并发:Web 服务器线程切换频繁。
  • 实时系统:尽量减少切换。
  • 面试点
  • 问“定义”时,提保存和加载。
  • 问“影响”时,提开销和优化。

总结

线程上下文切换是 OS 为多线程分配 CPU 的过程,保存当前线程状态并加载新线程,开销包括时间和缓存。Java 中由 OS 控制,影响性能和响应性。面试时,可提触发场景或优化方法,展示理解深度。