线程池的类关系

线程池的继承关系:

  • Executor:接口,只有一个方法 executor()
  • ExecutorService:接口,继承了 Executor 接口,增加了几个管理线程池的方法
  • ThreadPoolExecutor:线程池的的实现类

创建线程池的工具类:Executors
未命名绘图.png

线程池的创建

线程池支持两种方式:

  • 通过 ThreadPoolExecutor 构造方法自定义
  • 通过 Executors 工具类创建线程池

自定义线程池

通过 ThreadPoolExecutor 的构造方法自定义线程池:

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit, // 延时单位
  5. BlockingQueue<Runnable> workQueue,
  6. ThreadFactory threadFactory,
  7. RejectedExecutionHandler handler)
  • corePoolSize:int,核心线程数,线程池在完成初始化后,默认情况下,线程池中并没有任何线程,线程池会等待有任务到来时,再去创建新线程执行任务
  • maximumPoolSize:int,最大线程数,线程池所能持有线程的最大数量
  • keepAliveTime:long,保持存活时间,当线程池的线程数多于 corePoolSize ,那么多余的线程空闲时间超过 keepAliveTime 时,它们就会被终止
  • workQueue:BlockingQueue,任务存储队列,常见的工作队列有三种
    • 直接交接:SynchronousQueue
    • 无界队列:LinkedBlockingQueue
    • 有界队列:ArrayBlockingQueue
  • threadFactory:ThreadFactory,当线程池需要新的线程的时候,会使用 threadFactory 来生成新的线程
    • 新的线程默认是由 ThreadFactory 创建的,默认使用 Executors.defaultThreadFactory() ,创建出来的线程都在同一个线程组,拥有同样的 NORM_PRIORITY 优先级并且均非守护线程
    • 自定义 ThreadFactory 时,就可以指定线程名、线程组、线程优先级、是否为守护线程等
  • Handler:RejectedExecutionHandler,当线程池无法接受新任务的拒绝策略

增减线程的特点:

  • 通过设置 corePoolSize 和 maxPoolSize 相同,就可以创建固定大小的线程池
  • 线程池希望保持较小的线程数,只有在负载变得很大时才增加它
  • 通过设置 maxPoolSize 为很大的值,如 Integer.MAX_VALUE ,可以允许线程池容纳任意数量的并发任务
  • 只有在队列填满时才创建多于 corePoolSize 的线程,所以使用无界队列时(如 LinkedBlockingQueue ),那么线程数就不会超过 corePoolSize

线程池的定义:建议自定义

  • 根据业务场景,设置合适的线程池参数
  • 设置合适的线程数量:
    • CPU 密集型(加密、计算 hash 等):最佳线程数为 CPU 核心数的 1-2 倍
    • IO 密集型(读取数据库、文件、网络读写等):最佳线程数一般会大于 CPU 核心数很多倍,以 JVM 线程监控显示繁忙情况为依据,保证线程空闲可以衔接上,参考 Brain Goetz 推荐的计算方法:线程数 = CPU 核心数 * (1 + 平均等待时间/平均工作时间)

通过 Executors 工具类创建

  • Executors.newFixedThreadPool():
    • 特点:创建固定大小的线程池(corePoolSize == maxPoolSize),使用无界队列 LinkedBlockingQueue
    • 缺点:由于 LinkedBlockingQueue 是没有容量上限的,当请求数越来越多并且无法及时处理完毕时,即请求堆积,会容易占用大量的内存,导致 OOM
  • Executors.newSingleThreadExecutor():
    • 特点:只有一个线程,使用无界队列 LinkedBlockingQueue
    • 缺点:同上,请求堆积,占用大量的内存,导致 OOM
  • Executors.newCachedThreadPool():new ThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,SynchronousQueue())
    • 特点:可缓存线程池;无界线程池,能够自动回收多余线程;使用的是 SynchronousQueue(只能持有一个任务)
    • 缺点:maxPoolSize被设置成了 Integer.MAX_VALUE,可能会创建大量的线程,甚至导致 OOM
  • Executors.newScheduledThreadPool()
    • 特点:支持定时及周期性任务执行的线程池

线程池使用

使用线程池的注意点:

  • 避免任务堆积
  • 避免线程数过度增加
  • 排查线程泄露

线程复用:ThreadPoolExecutor.Worker.runWorker()方法,通过使用当前线程执行 run() 方法实现线程复用

线程添加规则

线程添加规则:

  • 如果线程数小于 corePoolSize,即使其他工作线程处于空闲状态,也会创建一个新线程来运行新任务
  • 如果线程数等于(或大于) corePoolSize 但少于 maxPoolSize ,则将任务放入队列

线程池提交新任务.png

线程池的拒绝策略

线程池拒绝时机:

  • 当 Executor 关闭时,提交新任务会被拒绝
  • 当 Executor 的工作队列已满,线程已经达到最大线程时

线程池的拒绝策略:

  • AbortPolicy:直接抛出异常
  • DiscardPolicy:直接丢弃
  • DiscardOldestPolicy:丢弃旧任务,保留新任务
  • CallerRunsPolicy:将任务交给提交的线程去运行

关闭线程池

停止线程池:

  • shutdown:停止线程池,拒绝接受新任务,但会继续执行任务队列中的任务
  • isShutDown:判断线程池是否停止,即是否还能接受新任务,并不是线程池完全停止
  • isTerminated:判断线程池是否完全停止
  • awaitTermination:检测线程池是否完全停止,延时检测
  • shutdownNow:立即关闭线程池,中断正在运行的线程,返回队列中的任务