线程池概述

线程池

池的意义

在资源不足以满足当前需求时,将资源圈定为池,反复利用(计划经济),从而满足当前需求,从本质上来讲是一种以时间换空间的做法。

线程池的意义

线程池,是在存在大量任务的前提下,固定一定数量的线程去执行这些任务;提高了线程本身的利用率,减少了大量创建销毁线程所带来的性能开销问题。、

线程池解决的问题

  • 反复创建线程开销大 -> 线程池只创建少量线程并反复利用去执行任务、
  • 过多的线程会占用较大的进程内存 -> 线程池同时存货的线程只有少量

线程池的好处

  • 合理利用资源(CPU和内存)
  • 统一管理资源
  • 加快程序的响应速度(减少线程创建 -> 减少资源开销 -> 加快程序执行速度)

线程池的创建和停止

线程池的创建

构造函数参数
  • corePoolSize:核心线程数,默认情况下,线程池中存活的最少线程数量,但是如果allowCoreThreadTimeout设置为true,那么核心线程在空闲时也会被回收
  • maxPoolSize:最大线程数,在核心线程和任务存储队列都工作饱和的情况下,线程池会再创建线程,此时线程池中线程上限为最大线程数;workQueue非无界队列时该参数才有意义
  • keepAliveTime:非核心空闲线程的保持存活时间,目的是回收空闲的非核心线程,保持线程池的线程数量,减少线程池资源消耗
  • workQueue:任务存储队列,如果核心线程都在执行任务,那么会先把任务放入workQueue中,等待线程池线程来执行
  • threadFactory:线程创建的工厂,默认工厂Executors.defaultThreadFactory创建出的线程都在同一个线程组,有同样的优先级且都不是守护线程
  • handler:线程拒绝策略,当线程池已经关闭,或核心线程、非核心线程和工作队列都已满时,对新提交的工作任务执行的拒绝策略
线程池创建方法的选择
  • 手动创建:在明确业务场景的情况下最好是手动创建线程池,可以让我们更明确当前线程池的运行规则,避免资源耗尽等风险
  • 自动创建:方便,但是有各自的危害
    • FixedThreadPool:core = max,但是workQueue是LinkedBlockingQueue,那么在线程处理速度赶不上任务提交速度时,极有可能OOM
    • SingleThreaPool:core = max = 1,workQueue为LinkedBlockingQueue,和Fix一样,可能OOM
    • CachedThreadPool:该线程池和上述两个线程池不同,它的主要风险点在于maxPoolSize,该参数被设置为Integer.MAX_VALUE,且因为workQueue为SynchronousQueue,只中转任务,最后会导致线程一直被创建,最终OOM
    • SheduleThreadPool:该线程池的主要问题在于参数workQueue,它的容量只有16,但是以150%的比率进行resize,上限为Integer.MAX_VALUE,如果被不断提交一些执行时间/周期较长的任务,在线程数量或队列容量方面都有可能导致OOM,且workQueue导致maxPoolSize无意义

线程添加规则

  • 如果线程数小于corePoolSize,那么在新任务的情况下,即使存在空闲线程,也会创建新线程来执行任务
  • 如果线程数等于/大于corePoolSize,但是小于maxPoolSize,且任务存储队列未满,那么会将新任务放入任务存储队列中
  • 如果线程数等于/大于corePoolSize,但是小于maxPoolSize,且任务存储队列已满,则创建新线程执行任务
  • 如果线程数等于/大于maxPoolSize,且任务存储队列已满,则对新任务执行线程拒绝策略

线程增减特性

  • coorPoolSize = maxPoolSize,即固定线程数的线程池
  • 线程池希望保持较小的线程数,所以只在必要时(负载变得很大)才增加线程数
  • 通过提高设置maxPoolSize,比如Integer.MAX_VALUE,可以让线程池处理任意数量的并发任务
  • 通过设置workQueue为无界队列,可以让线程池容纳任务数量的任务

常见工作队列

  • SynchronousQueue:直接交接队列,对工作任务只做中转,不存储任务,会阻塞任务提交线程/任务处理线程
  • LinkedBlockingQueue:无界队列,如果线程处理速度小于任务提交速度,会导致队列中任务一直增加,最终造成OOM;该队列让maxPoolSize无效
  • ArrayBlockingQueue:有界队列,可以在生成线程池时提前设置好队列大小,是比较常用的一种工作队列

线程数量的计算

  • CPU密集型任务:比如加密、计算等相关任务,这些任务会频繁的使用CPU,那么此时我们可以直接将线程数量设置为CPU数量的一至两倍,最大程度上利用CPU资源
  • IO密集型任务:比如读写数据库、读取文件、网络读写等相关任务,这些任务会经常在等待某些资源,那么线程数可以设置为CPU的很多倍,保证CPU不会处在等待期间内,具体的一个数量可以通过压测得出

Brain Goetz的计算公式:线程数 = CPU核心数 * (1 + 平均等待时间 / 平均工作时间)平均等待时间代表CPU的等待时间,平均工作时间代表CPU的工作时间,前者越大说明CPU需要等待的时间越长,可以把线程数量设置的大一些,尽量不让CPU空闲;后者越大则说明使用CPU的时间越长,线程数量需要设置的小一些,因为CPU已经满负荷运转了,设置再多的线程也只是徒劳,只会给内存增加压力

线程池关闭的相关方法

  • shutdown:该方法仅仅是初始化整个关闭过程,会将正在执行的任务和队列中的任务执行完,对新任务会执行rejectHandler
  • isShutdown:判断线程池是否被标记为关闭状态
  • isTerminated:判断整个线程池是否已经完全停止了,这里的完全停止意味着正在执行和任务队列中的任务都已完成,线程池已关闭
  • awaitTermination:检测线程池是否完全关闭,会根据传入的时间参数阻塞调用线程
  • shutdownNow:暴力结束线程池,会对正在执行任务的线程调用interrupt(),并以List<Runnable>的形式返回任务队列中的所有任务,但是需要注意,这并不意味着该线程池已经terminated,有些线程还在执行中,因为interrupt()方法不会立刻停止线程

线程池的拒绝策略

拒绝时机
  • 线程池已经被关闭
  • maxPool和workQueue都已满
拒绝策略
  • AbortPolicy:抛出RejectExecutionException,相当于一个通知去告知任务提交方,线程池不执行这个任务
  • DiscardPolicy:默默丢弃任务,不通知任务提交方
  • DiscardOldestPolicy:抛弃工作队列中最老的任务(一般为队列头),把新提交的任务加入工作队列中
  • CallerRunsPolicy:让任务提交方去执行当前任务

线程池的钩子

钩子,即任务执行前后进行的一些操作,相当于给线程池加了个切面,这是ThreadPoolExecutor中提供的一些方法,可以通过重写去定义相关操作

  • beforeExecution
  • afterExecution

线程池的组成部分

线程池主要由以下四部分组成

  • 线程池管理器
  • 工作线程
  • 工作队列
  • 任务接口

线程池中线程的复用

线程池的线程,比如corePool中的线程,都是已经被调用过start(),这些线程的作用(run())是,不断的去任务队列中获取任务,然后直接调用任务的run(),让任务执行

线程池的状态

  • RUNNING:接受新任务并执行
  • SHUTDOWN:不接受新任务,但是会将线程正在执行的和任务队列中的任务执行完,调用shutdown()后线程池会处在该状态
  • STOP:不接受新任务,泛起并返回工作队列中所有未执行任务,并调用正在执行任务的线程的interrupt()方法,调用shutdownNow()后线程池会处在该状态
  • TIDYING:任务队列为空,工作线程数为0,该状态后线程池就会调用terminated()方法真正终止线程池
  • TERMINATED:terminated()方法运行完成后,线程池转变到该状态

使用线程池的注意点

  • 避免任务堆积
  • 避免线程数过度增加
  • 避免线程泄露,即因为任务逻辑或某些异常原因,导致线程池中的线程数量和预期的不符
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值