线程场景

  • IO密集型任务: 任务类型主要是IO操作,CPU利用率低.
  • CPU密集型任务: 任务类型主要是计算工作,响应快,CPU会一直工作,其利用率也很高.
  • 混合型任务: 任务包含了IO操作和逻辑运算,由于IO操作的成本通常比逻辑操作的高出非常多.所以此类型任务,多数情况也是CPU利用率不高.

如何根据不同的场景构建合适的线程池

下面是基础定义:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

IO密集型任务

由于CPU使用率不高,且每个人物CPU工作时间较短,可以增加线程池中工作的线程,提高执行效率

  1. corePoolSize为CPU核心线程数2倍
  2. corePoolSize=maximumPoolSize,保证优先使用扩展线程的方式分配任务,而非进入阻塞队列
  3. workQueue必须使用有界队列

CPU密集型任务

CPU密集型任务,单个任务需要占用CPU较长的工作时间,如果线程池中工作的线程过多,会导致CPU更频繁的切换任务,这种时间消耗会使任务效率下降.所以定的时候,线程数等于CPU核心数就行.

  1. corePoolSize为CPU核心线程数
  2. corePoolSize=maximumPoolSize,保证优先使用扩展线程的方式分配任务,而非进入阻塞队列
  3. workQueue必须使用有界队列

混合型任务

混合型任务可以通过公式计算出最佳线程数:

最佳线程数 = ((线程等待时间+线程CPU时间) / 线程CPU时间) * CPU核数

或:

最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1) * CPU核数

例如:WEB应用的HTTP请求,当请求到达后端时,任务的核心流程主要有两部分组成:

  1. 业务代码的执行,通常耗时较短(假定全程需要100ms)
  2. RPC,DB等IO操作,CPU几乎不工作,只需要等待资源(假定资源获取时cpu需要等待500ms)
  3. CPU物理核心为8core

    此时理想线程数为: (500 + 100) / 100 * 8 = 48

  • PoorSize: PoorSize所指的既是线程池中最佳的活动的线程数.由于poorSize是由线程池的维护,是在实时变动的,所以在配置corePoolSize,maximumPoolSize两个参数的时候,至少要保证poorSize数量. 根据线程池维护和创建规则(前文有介绍),可以设置成: corePoolSize=maximumPoolSize=48

    corePoolSize: 核心线程池大小,即在没有任务需要执行的时候线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。 maximumPoolSize: 线程池中允许创建的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数(poolSize)小于maximumPoolSize,那么会创建新的线程来执行任务。 poorSize: 线程池中当前线程的数量,当该值为0的时候,意味着没有任何线程,线程池会终止;同一时刻,poolSize不会超过maximumPoolSize。

小结

线程池参数的定义对程序运行多线程效率有很大的影响,根据不同的使用场景,配置合适的线程池是十分重要的.