《Java多线程编程实战指南(设计模式篇)》答疑总结(陆续更新,part2)

阅读更多

什么是上下文切换,哪些因素会导致上下文切换,它有哪些开销,如何降低这些开销(2015-12-01)?

《Java多线程编程实战指南(设计模式篇)》作者回复:
《Java多线程编程实战指南(设计模式篇)》第1章打了个比方:比如我们用手机与他人通话的时候,聊着聊着的时候由于第3个人拨打了你的电话。那么,这个时候你可能会做的一个动作就是先记下刚才的通话聊到哪里的(即进度),接着和对方说“我先接个电话,你别挂断“,然后和第3个人说”稍后给您回电“。这时,继续和第1个人通话的时候,我们就要回想刚才我们聊到哪里了,否则你必然要问对方这个问题。

上面的比方中,从和一个人通话到和另外一个人通话的这种来回切换就是上下文切换,通话中聊到哪里的这个信息就是”上下文”。

从Java语言的角度来说,上下文切换就是Java线程的状态在Runnable和非Runnable(Blocked、WAITED或者TIMED_WAITED)之间来回变动时CPU所做的一个动作。线程的状态从Runnable到非Runnable的变化时CPU所在的动作叫切出(Switch out),线程的状态从非Runnable到Runnable的变化时CPU所在的动作叫切入(Switch in)。

上下文切换的开销包括直接开销和间接开销。直接开销就是保存或者恢复被切出或者切入的线程的上下文信息的CPU开销。上下文信息包括寄存器、程序计数器、栈指针等信息。

间接的开销包括线程重新调度所需的开销、以及被切出的线程可能会被切入到另外一个CPU上运行而导致CPU Cache Miss导致的开销(即Cache Miss导致读内存这个慢的操作的时间开销)。

根据上述描述,我们可以发现凡是可以导致线程状态从Runnable变为非Runnable的因素,都会导致上下文切换。这些常见因素包括:
I/O操作、
锁(非争用的锁由于Java做过优化,并不会导致上下文切换)、
GC(GC的时候所谓的stop-the-world会导致所有应用线程被暂停,即切出)。

如何减少上下文切换的开销问题实际上就是如何减少上下文切换的问题。常见的方法包括:
减少锁持有的时间:锁持有的时间越短,它被争用的几率越低,因此它导致上下文切换的几率也越低。这与Java1.6 开始对synchronized做的一些优化有关。因为Java会对持有时间较短的锁做这样一种优化(可以理解为spin-wait),使得这种锁的获得不会导致上下文切换。
避免在临界区中执行I/O操作:因为I/O操作会引起上下文切换,而I/O操作通常又是比较慢的操作,若将这种操作放在临界区中会使得锁的持有时间变长,从而使得锁的获得也会上下文切换。于是就产生了雪上加霜的效果。
尽量减少GC的频率,有其是Full GC的频率:GC过程中的compact阶段由于要移动对象的内存区域导致它必须停止应用线程,从而导致上下文切换。

在Linux平台下,我们可以使用perf命令来监控发生在指定进程内的上下文切换。
对于Java8,我们可以使用JMC这个JDK工具来查看上下文切换的频率。

 

你可能感兴趣的:(多线程,java,设计模式,编程)