最近在复习线程池, 看到一个问题:线程池中的线程数量大小设置为多少合适?
javaguide里给出的回答:
- 设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率
众所周知的前提:
JVM采用的是轮循机制调度,每个线程都分配一个时间片,以在CPU上运行。当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。概括来说就是:当前任务在执行完CPU时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
我就开始琢磨一个问题,如果线程池中只有一个线程,这个线程也会因为CPU时间片用完而挂起,也就可以说,一个线程也会存在频繁的上下文切换。
那么,一个线程和1000个线程有什么区别呢,他们都有频繁的上下文切换不是吗。
原来,是我只考虑了寄存器状态,程序计数器,而没有注意到还要保存/恢复栈信息。
我找了许多资料,终于看见一篇文章解释了。
为什么线程过多会损害性能
以下是原文翻译:
CPU时间片轮循制度也会带来一些开销。
最直观的开销是在切换线程时保存和恢复线程的寄存器状态。不过,由于调度器通常会分配足够长的时间片,使得保存和恢复的开销相对较小,因此这个开销并不是主要问题。
一个更微妙但重要的开销是缓存状态的保存和恢复,缓存状态可能占用数兆字节(MB)的空间。现代处理器非常依赖缓存,缓存的访问速度可能比主存快 10 到 100 倍。缓存命中不仅能大幅提高访问速度;还能节省内存总线的带宽。缓存虽然快速,但容量有限。当缓存满了时,处理器必须从缓存中驱逐一些数据,以便为新数据腾出空间。通常,驱逐的策略是最近最少使用(LRU),也就是优先淘汰来自较早时间片的数据。因此,软件线程往往会互相驱逐彼此的数据,而过多的线程导致的缓存竞争可能会降低性能。
类似的开销也存在于虚拟内存中。大多数计算机使用虚拟内存技术。虚拟内存主要存储在磁盘上,而常用部分则保存在实际内存中。与缓存类似,当需要腾出空间时,最近最少使用(LRU)的数据会从内存中驱逐到磁盘。每个软件线程都需要虚拟内存来存储其栈和私有数据结构。与缓存一样,时间片会导致线程之间争夺实际内存,从而降低性能。在极端情况下,如果线程太多,程序甚至可能耗尽虚拟内存。
另一个问题是当持有锁的线程被抢占时会发生什么。所有等待锁的线程现在都必须等待持有锁的线程再次获得时间片并释放锁。如果锁实现是公平的,即按照先到先得(FIFO)的顺序分配锁,则问题会更严重。如果等待锁的线程被抢占,则会阻止后面所有等待锁的线程获得锁。这就像有人在结账队伍中睡着了一样。没有足够硬件线程来运行它们的软件线程越多,这就越有可能成为问题。