JUC-线程池-0-介绍

在日常开发中,当我们需要使用多线程来处理一些任务的时候,可以创建一个线程,然后去调用 start() 启动,这种方式比较简单,但是同时也带来一些问题,比如在任务数量多的情况下,频繁的创建销毁线程,就会给服务器带来较大的压力,处理效率也不是特别高, 所以在这种情况下,我们就可以使用线程池来处理这些任务,创建固定数量的线程去处理任务,避免一直创建和销毁线程带来的开销. 下面就对线程池做一些详细的了解.

线程池的作用

线程池的作用就是创建一定数量的线程,这些线程都保持工作的状态,有任务进来就交给这些线程,这些线程都可以去反复执行任务,从而避免反复创建线程的损耗.

使用线程池可以加快响应速度,合理利用CPU和内存,且可以统一管理

线程池的适用场景

当服务器收到大量的请求时,使用线程池技术就比较合适,可以减少线程的创建和销毁次数,提高服务器的工作效率,在日常开发中,如果需要创建5个以上的线程,就可以使用线程池来管理

创建和停止线程池

上面说了这么多线程池的好处,下面来看一下如何创建线程池, 首先要看一下线程池的构造函数

线程池的构造函数

进入 ThreadPoolExecutor 这个类看一下它的构造函数, 如图

image

这里可以看到一共有以下几个参数,分别看一下具体的意思

参数名 作用
corePoolSize 核心线程数
maximumPoolSize 最大线程数
keepAliveTime 保持存活时间
unit 时间单位
workQueue 任务存储队列
threadFactory 创建线程工厂
handler 线程池无法接受新的任务的时候的拒绝策略

那么这些参数具体的作用是什么? 首先是 corePoolSizemaximumPoolSize

  • corePoolSize: 核心线程数,线程池在初始化完成之后,默认情况下线程池中是没有线程的,线程池会等有任务到来的时候去创建新线程执行

  • maximumPoolSize: 在线程池运行过程中,可能会在核心线程数的基础上再创建一些线程,但是总的线程数有一个上线,就是 maximumPoolSize

  • keepAliveTime: 就是在核心线程以外的线程的回收时间,当核心线程数意外的线程空闲超过 keepAliveTime 这个时间的时候就会被回收

  • workQueue: 存放任务的队列,当核心线程都有任务在执行的时候,新来的任务就会被放到任务队列中等待执行
  • threadFactory: 创建线程的工厂,如果不设置的话,默认是 DefaultThreadFactory ,通常情况下也不会去自定义
  • handler: 线程池处理任务达到极限的时候就不能再接受新的任务了,这个参数就是设置拒绝的策略

创建线程的规则

了解了构造函数中参数的作用之后,再来了解一下线程池创建线程的规则

当有新的任务进来的时候,会有以下判断:

  1. 判断线程池当前线程数是否小于 corePoolSize, 如果当前线程数量小于核心线程数量,就会新建一个线程来运行
  2. 如果线程数大于等于 corePoolSize 但小于 maximumPoolSize ,就会将任务放到队列中去
  3. 如果队列满了,并且线程数小于 maximumPoolSize 就会创建新的线程来执行
  4. 如果队列满了,并且线程数大于等于 maximumPoolSize 就拒绝该任务

画个图来看一下这个流程

image

所以判断是否新创建线程的顺序是 corePoolSize -> workQueue -> maximumPoolSize

举个例子,现在有一个 corePoolSize=5, maximumPoolSize=10, workQueue=100 的线程池,有一批任务提交给这个线程池, 然后前五个任务都会创建新的线程去执行,然后后面的任务都会先存到队列里面,直到队列里面放了100个任务,队列满了之后,再进来的任务将会创建新的线程执行,最多创建10个,之后再有任务进来,就会拒绝,所以这个线程池最多能同时处理 115 个任务

增减线程的特点

通过上面这个创建线程的规则,可以看出以下几个线程池的特点

  1. 创建线程池的时候 corePoolSizemaximumPoolSize 设置为相同的时候,就是一个固定线程数量的线程池
  2. 线程池希望保持较小的线程数,只有在任务特别多,负载高的情况下再创建额外的线程去处理
  3. maximumPoolSize 可以设置为很高的值,比如 Integer.MAX_VALUE, 就表示线程池可以容纳任意数量的任务
  4. 只有当 workQueue 满了的时候才会创建额外的线程去处理,所以如果使用的是无界队列,线程池的线程数量就不会超过 corePoolSize

常见的队列类型有以下三种:

  • 直接交接: SynchronousQueue
  • 无界队列: LinkedBlockingQueue
  • 有界队列: ArrayBlockingQueue

这里的直接交接就是这个队列不存放任务,直接交给线程池去执行

总结

  • 了解线程池的作用,以及线程池解决了哪些问题
  • 创建线程池的几个关键参数
  • 线程池创建线程的规则