NightPxy 个人技术博客

线程池

Posted on By NightPxy

线程池

线程池的优势

  • 线程是一种昂贵的资源.创建和销毁都是非常昂贵的.所以需要线程池来更好的减少和复用线程
  • 提前初始化一部分线程,可以跳过从头创建从而快速申请到执行
  • 对线程的总体数量进行把控,避免过度开启线程

线程池的原理和执行流程

线程池一般由 工作线程池阻塞队列 两大部分组成

  • 工作线程池
    包含线程池中实际执行的所有线程.一般会有初始数量(期望数量)和最大数量对其进行控制
  • 阻塞队列
    所有执行单元的提交,都会由阻塞队列进行暂存.阻塞队列一般有规定上限,以及达到上限如何处理的拒绝策略等

所有整个线程池的执行流程是

  • 初始化线程池.可选提前创建部分线程(接近期望数量)
  • 等待接受执行单元.
    一旦收到执行单元就会查看当前工作线程池的数量
    1.如果在期望数量之下,无论是否有空闲线程都会创建新线程并执行.(最终达到期望数量)
    2.如果在期望数量和最大数量之间,就会尝试寻找空闲线程.如果能找到空闲线程就会直接执行,如果不能找到空闲线程就会创建新线程
    3.如果在最大数量之上,就会交给阻塞队列
  • 执行单元交给阻塞队列后,????
  • 然后按照阻塞队列的策略(例如FIFO),在工作线程池中找一个空闲的线程进行执行. 如果工作线程池中找到一个空闲线程,就会标记这个线程并使用这个线程进行执行
  • 工作线程池本身会定时检查空闲线程,如果达到最大空闲时间依然没有任务执行就会销毁,直到工作线程池数量最终慢慢回归到期望数量

线程池大小设置

根据实际线程池的使用场景决定

  • 如果是CPU密集型 就应该适当的减少线程池数量.
    因为此时提高线程数量无助于提升执行效率,反而因为过多的线程切换影响效率
  • 如果是IO密集型 就可以适当的提高线程池数量
    因为在IO密集的等待时间中,并行的尝试执行其它的任务

线程池的实现

ThreadPoolExecutor

线程池签名

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

corePoolSize
线程池的期望线程数.在空闲时,线程池最终会慢慢回到期望线程数

maximumPoolSize
线程池最大线程数

keepAliveTime & unit
空闲线程的保留时间 & 空闲线程保留时间的单位 超出期望线程数(CorePoolSize)的线程,会在该时间后被自动销毁

workQueue 阻塞队列 每个提交的执行单元都会进入阻塞队列缓存,再由线程池决定何时使用哪个空闲线程进行执行
阻塞队列可以设置上限,在达到上限后继续进行提交就会触发拒绝策略
阻塞队列有几个可选实现:

  • ArrayBlockingQueue 有界队列 由数组实现的阻塞队列,FIFO.数组实现,需要指定队列数量,是为有界队列
  • LinkedBlockingQueue 无界队列
    由链表实现的阻塞队列,FIFO,链表实现,默认以Integer.MAX_VALUE,是为无界队列
  • SynchronousQueue 无队列(无界队列)
    不使用(上限为0)的阻塞队列,当提交后将立即寻找一个工作线程执行,如果没有找到会立即创建
  • PriorityBlockingQueue
    按照优先级阻塞

threadFactory
线程创建工厂
如果线程池没有达到上限又提交了新的执行单元,就会使用该工厂创建一个线程加入线程池并执行

RejectedExecutionHandler handler
执行拒绝策略
在提交执行单元到阻塞队列时,如果达到阻塞队列上限,就按该拒绝策略决定如何执行
可选的拒绝策略有

  • AbortPolicy(默认)
    拒绝后直接抛出异常告知调用者
  • CallerRunsPolicy 使用调用者
    拒绝后不使用线程池执行,而是交回调用者线程池执行
  • DiscardOldestPolicy 抛弃队列中最久的任务
    拒绝后丢弃最老的执行单元,然后重新加入队列
  • DiscardPolicy 抛弃当前任务
    拒绝后直接丢弃当前任务.不抛出异常

线程池的关闭

shutdown & shutdownNow
都会遍历所有线程interrupt中断线程

区别

  • shutdown
    仅停止阻塞队列中等待的线程,而正在执行中的线程会等待完成
  • shutdownNow
    会强行停止所有的线程

其它线程池实现

JDK还提供其它线程池共计四种

  • FixedThreadPool
  • CachedThreadPool
  • SingleThreadExecutor
  • ScheduledThreadPool

这四种线程池本质都是ThreadPoolExecutor策略切换(参数切换实现切换)

FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
  • 期望数量与最大数量都是nThreads的固定线程数量线程池. 这样线程池中线程数量实际将始终等于最大数量(或者说期望数量)
  • 没有过期时间,但实际其实无效.因为期望数量与最大数量相同,这样线程空闲也不会销毁
  • LinkedBlockingQueue 阻塞队列为无界队列,FixedThreadPool允许无限缓存等待执行单元
    也就是它不会拒绝任务

CachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 期望数量为0,最大数量为Integer.MAX_VALUE,说明线程数量是运行无限开启的,但允许销毁
  • 60秒过期,说明所有空闲线程最终会在60秒后全部被销毁
  • SynchronousQueue 也是一个无界队列.说明它也不会拒绝任务
    每一个任务都会使用空闲或者创建一个新线程来执行

SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 期望数量为1,最终数量为1. 说明线程池线程数量固定为1,并且也不会销毁
  • LinkedBlockingQueue 无界队列,也不会拒绝任务.

ScheduledThreadPool

两种定期间隔调度策略

  • scheduleWithFixedDelay
    以间隔频率调度执行.(会在任务真正执行完毕后开始计算间隔时间再执行)
  • scheduleAtFixedRate
    以固定频率调度执行.(不会考虑实际的任务执行,可能会重复同时执行)

内部封装了

线程池实现的选择

按照阿里的开发规范,不建议使用四种默认实现而始终使用ThreadPoolExecutor本身
这样的目的是通过手动调整参数来完全达到自己的目的,避免性能问题

  • CachedThreadPool
    可以无限创建线程,非常容易出问题
  • FixedThreadPool,SingleThreadExecutor
    这两个虽然都不会不限创建线程,但是其阻塞队列是无界的,无限缓存而不拒绝可能会造成OOM
  • ScheduledThreadPool
    内部依然是可以无限创建线程 …..????

https://zhuanlan.zhihu.com/p/33264000 https://blog.csdn.net/qq_25806863/article/details/71126867 https://blog.csdn.net/wang_rrui/article/details/78541786