高并发系统:池化技术-JDK线程池

JDK 实现的这个线程池优先把任务放入队列暂存起来,而不是创建更多的线程,它比较适用于执行 CPU 密集型的任务,也就是需要执行大量 CPU 运算的任务。这是为什么呢?因为执行 CPU 密集型的任务时 CPU 比较繁忙,因此只需要创建和 CPU 核数相当的线程就好了,多了反而会造成线程上下文切换,降低任务执行效率。所以当前线程数超过核心线程数时,线程池不会增加线程,而是放在队列里等待核心线程空闲下来。

一般核心线程数与CPU核数一致,计算公式:
线程数目 = CPU核数 * CPU 利用率 * (1 + 等待时间 / CPU计算时间)

JDK线程池核心参数

1
2
3
4
5
6
7
8
//java.util.concurrent.ThreadPoolExecutor
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
  • **corePoolSize:**线程池中的核心线程数,即使没有任务执行的时候,他们也是存在的.(不考虑配置了参数:allowCoreThreadTimeOut,allowCoreThreadTimeOut通过字面意思也能知道,就是是否允许核心线程超时,一般情况下不需要设置,本文不考虑)
  • **maximumPoolSize:**线程池中的允许存在的最大线程数
  • **keepAliveTime:**当线程池中的线程超过核心线程数的时候,这部分多余的空闲线程等待执行新任务的超时时间.例如:核心线程数为1 ,最大线程数为5,当前运行线程为4,keepAliveTime为60s,那么4-1=3个线程在空闲状态下等待60s 后还没有新任务到来,就会被销毁了.
  • **unit:**keepAliveTime 的时间单位
  • workQueue: 线程队列,如果当前时间核心线程都在运行,又来了一个新任务,那么这个新任务就会被放进这个线程队列中,等待执行.
  • threadFactory: 线程池创建线程的工厂类.
  • handler: 如果线程队列满了同事执行线程数也达到了maximumPoolSize,如果此时再来新的线程,将执行什么 handler 来处理这个线程. handler的默认提供的类型有:
    • AbortPolicy: 抛出RejectedExecutionException异常
    • DiscardPolicy: 什么都不做.
    • DiscardOldestPolicy: 将线程队列中的最老的任务抛弃掉,换区一个空间执行当前的任务.
    • CallerRunsPolicy: 使用当前的线程(比如 main)来执行这个线程.

JDK线程创建回收策略

  1. **<corePoolSize:**如果新加入一个运行的任务,当前运行的线程小于corePoolSize,这时候会在线程池中新建一个线程用于执行这个新的任务.
  2. **>corePoolSize,队列不满:**如果新加入一个运行的任务,当前运行的线程大于等于corePoolSize,这个时候就需要将这个新的任务加入到线程队列workQueue中,一旦线程中的线程执行完成了一个任务,就会马上从队列中去一个任务来执行.
  3. **>corePoolSize,<maximumPoolSize:**如果队列也满了,怎么办呢? 如果maximumPoolSize大于corePoolSize,就会新建线程来处理这个新的任务,直到总运行线程数达到maximumPoolSize.
  4. **>maximumPoolSize:**如果总运行线程数达到了maximumPoolSize,还来了新的任务怎么办呢?就需要执行上面所说的拒绝策略了handler了,按照配置的策略进行处理,默认不配置的情况下,使用的是AbortPolicy.
  5. **keepAliveTime:**超过corePoolSize的线程,在空闲时间超过keepAliveTime时会被释放
  6. **allowCoreThreadTimeOut:**在配置了allowCoreThreadTimeOut时,corePoolSize线程在空闲时也会释放,一般不配置。