当一个线程实验获取一个已经被其他线程持有的锁时,它会被阻塞并进入等待状态,直到该锁被开释。这个过程需要操作体系的参与:
挂起线程:操作体系将当前运行的线程从 CPU 上移除,并将其状态保存到线程控制块(Thread Control Block, TCB)中。这个过程涉及到上下文切换,即保存当前线程的上下文(如寄存器、步伐计数器等)并加载下一个要运行线程的上下文。
叫醒线程:当锁被开释时,操作体系将叫醒等待获取该锁的线程,将其状态从等待队列中移除,并重新调度该线程运行。
3.1 上下文切换
上下文切换是指操作体系将一个正在运行的线程换出 CPU,并将另一个线程调入 CPU 运行的过程。上下文切换包括保存当前线程的状态(如寄存器值、步伐计数器等),并加载下一个线程的状态。上下文切换的重要步调如下:
保存当前线程的上下文:操作体系保存当前线程的寄存器值、步伐计数器等状态到其线程控制块(TCB)中。
选择下一个线程:操作体系根据调度算法选择下一个要运行的线程。
加载新线程的上下文:操作体系从选定线程的 TCB 中恢复其寄存器值、步伐计数器等状态。
切换到新线程:操作体系将 CPU 切换到新线程,开始实行。
3.2 上下文切换的开销
上下文切换是一个开销较大的操作,重要包括以下几个方面:
CPU 时间:保存和恢复线程的上下文需要耗费 CPU 时间。
缓存失效:上下文切换可能导致 CPU 缓存失效,需要重新加载缓存数据。
TLB 刷新:上下文切换可能导致 TLB(Translation Lookaside Buffer)刷新,从而增加内存访问延迟。
在高并发环境下,频仍的上下文切换会明显低落体系性能。因此,淘汰上下文切换是提高多线程应用性能的重要本领之一。
4. 用户态与内核态的转换
线程挂起和叫醒的操作需要从用户态(user mode)转换到内核态(kernel mode),这是因为这些操作需要操作体系内核的支持。
用户态:应用步伐运行的模式,具有受限的访问权限,不能直接访问硬件或内核数据结构。
内核态:操作体系内核运行的模式,具有完全的访问权限,可以实行任何 CPU 指令并访问任何内存地址。
用户态到内核态的转换涉及以下步调:
陷入(Trap)指令:当线程需要举行体系调用(如线程挂起或叫醒)时,会触发一个陷入指令,使 CPU 从用户态切换到内核态。
保存上下文:操作体系内核保存当前线程的上下文,包括寄存器、步伐计数器等。
实行内核代码:操作体系内核实行挂起或叫醒线程的代码。
恢复上下文:操作体系内核恢复目的线程的上下文。
返回用户态:实行返回指令,将 CPU 从内核态切换回用户态。
4.1 体系调用
上下文切换的重要开销来源包括:
CPU 时间:保存和恢复线程的上下文需要耗费 CPU 时间。
缓存失效:上下文切换可能导致 CPU 缓存失效,需要重新加载缓存数据。
TLB 刷新:上下文切换可能导致 TLB(Translation Lookaside Buffer)刷新,从而增加内存访问延迟。
5.2 高并发场景下的影响
在高并发场景下,频仍的上下文切换会明显低落体系性能。多个线程同时竞争 CPU 资源,导致频仍的线程切换,增加了 CPU 的开销。为了淘汰上下文切换带来的影响,可以通过优化锁机制和淘汰锁竞争来提高并发性能。
6. 早期版本的 synchronized 效率低的原因