多线程的上下文切换
约 859 字大约 3 分钟
2025-08-27
定义
上下文切换指 CPU 从一个线程切换到另一个线程时,保存当前线程的上下文状态(包括程序计数器、寄存器、栈指针等),并恢复目标线程上下文状态的过程。这允许多线程共享 CPU 时间片,但引入额外开销:
- 保存和恢复状态涉及内存访问和寄存器操作,增加 CPU 周期消耗。
- 相比单线程执行,多线程上下文切换的开销更大(需处理更多状态信息)。
- 频繁上下文切换会降低系统效率(如增加延迟、减少吞吐量),需优化以减少发生次数。
触发上下文切换的情况
上下文切换通常由操作系统调度器触发,具体场景包括:
时间片耗尽 操作系统为每个线程分配固定 CPU 时间片(例如 10 ms)。当时间片到期时,CPU 强制切换到其他就绪线程。
线程主动阻塞 线程因等待资源而主动让出 CPU,例如:
- 等待 I/O 操作(如文件读写或网络通信)。
- 获取锁失败(如 Java 的
synchronized或ReentrantLock)。 - 调用
sleep()或wait()方法,使线程进入休眠状态。
高优先级线程抢占 在抢占式调度系统中,更高优先级线程进入就绪状态时,CPU 立即中断当前线程,执行高优先级任务。
线程主动让出 线程调用方法主动释放 CPU(如 Java 的
Thread.yield()),使自身回到就绪队列,允许其他线程运行。中断处理 硬件事件(如磁盘 I/O 完成或网络数据到达)触发中断,CPU 暂停当前线程,执行中断服务程序(ISR)。
线程终止 线程执行完毕或异常退出时,释放 CPU 资源,调度器选择新线程运行。
减少上下文切换的策略
优化目标:最小化状态保存/恢复的开销,提升系统性能。关键策略包括:
合理控制线程数量
- 避免创建过多线程,尤其是高并发场景。
- 使用线程池(如
ThreadPoolExecutor)管理线程生命周期。 - 推荐线程数 = CPU 核心数 × (1 + 平均等待时间 / 平均计算时间)
使用无锁并发编程(Lock-Free Programming)
- 利用原子操作(如 CAS)代替传统锁机制。
- 避免线程因竞争锁而进入阻塞或等待状态。
- 典型实现:
AtomicInteger、ConcurrentHashMap(Java 8+)、StampedLock。
采用 CAS(Compare-And-Swap)算法
- CAS 是一种基于硬件支持的原子操作,用于实现无锁数据结构。
- 通过循环重试机制更新共享变量,而不是阻塞线程。
使用虚拟线程(Virtual Threads /协程)
- Java 19+ 引入的虚拟线程(Project Loom)是一种用户态线程。
- 由 JVM 而非操作系统调度,切换开销极低(通常为纳秒级)。
- 单个操作系统线程可承载成千上万个虚拟线程。
合理设计锁的粒度与范围
- 缩小同步块(
synchronized或Lock)的作用域,减少持有锁的时间。 - 避免在同步块中执行耗时操作(如 I/O、复杂计算)。
- 使用读写锁(
ReadWriteLock)提升并发度。 - 优先使用更细粒度的锁(如分段锁、无锁结构)。
- 缩小同步块(