线程池的原理及相关概念

我们创建线程池使用ThreadPoolExecutor类来创建,ThreadPoolExector继承自抽象类AbstractExecutorService类,该抽象类实现了ExecutorService接口,而它又实现了Executor接口。总的来说,线程池的本质是Executor接口,但我们不直接使用它来创建线程池,而是使用ThreadPoolExecutor来new以下。

  • Executor接口:里面有一个execute(Runnable)方法,返回值为void
  • ExecutorService接口:submit、invokeAll、invokeAny

Executors工具类中提供了几个静态方法创建线程池
在这里插入图片描述

ThreadPoolExecutor中常用方法

  • submit:向线程池提交线程请求,返回结果,由Future(异步方法调用)实现结果返回,这个方法本质也是调用execute方法
  • execute:向线程池提交线程请求
  • shutdown:关闭线程池
  • shutdownNow:关闭线程池

线程池是一种资源,使用完毕后需要关闭

ThreadPoolExecutor有四个构造方法,但前三个本质上都是调用第四个构造器来new,只不过缺省的参数采用默认值

  1. 核心池大小、最大线程池大小,超时时间、时间单位、阻塞队列
  2. 核心池大小、最大线程池大小、超时时间、时间单位、阻塞队列、线程工厂
  3. 核心池大小、最大线程池大小,超时时间、时间单位、阻塞队列、拒绝策略
  4. 核心池大小int、最大线程池大小int,超时时间long、时间单位TimeUnit、阻塞队列BlokingQueue(接口)、线程工厂ThreadFactory、拒绝策略RejectedExectionHandler(接口)

线程池原理:
我们创建线程池时,会设置以下七个参数:核心池大小、最大线程池大小、超时时间、时间单位、阻塞队列、线程工厂、以及拒绝策略。核心池大小是指在阻塞队列为空时,可并行执行的线程数量;最大线程池大小只是指实际线程池中最多允许并行执行的线程数量;阻塞队列是指,当没有足够多的线程数量满足线程请求时,线程请求进行排队等待形成的队列;超时时间是指,当线程请求多长时间未得到响应就放弃请求线程的时间,时间单位则对应设置超时时间的单位;线程工厂指为线程池创建线程的对象;拒绝策略是指线程并行量当达到线程池最大线程数量,且阻塞队列排满时,有新的线程请求到来时的处理策略。
当我们创建了一个线程池时,实际上池中并没有线程。只有当线程请求到来时,才会利用线程工厂动态的创建线程(当然也可以通过调用prestartAllCoreThreads()和prestartCoreThreads()来启动所有或者一个核心线程以备请求),直到达到核心池大小数目。这时,再来的线程请求会被放到阻塞对列中去排队。当阻塞队列满时,会检查是否达到线程池最大线程数,如果没有,那么创建线程来满足请求,如果达到了则采取拒绝策略处理新来线程请求。对于超时时间来说,只有当超过核心池大小是,阻塞队列才计时,但也可以通过调用allowCoreThreadTimeOut()来对排队线程请求直接生效。
在这里插入图片描述
在这里插入图片描述

时间单位
TimeUnit.DAYS; //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒

拒绝策略有四种(RejectedExecutionHandler调用rejectedExecution(Runnable,ThradPoolExecutor)方法)

  1. ThreadPoolExecutor.AbortPolicy(默认):拒绝服务,抛弃并抛出RejectedExectionException异常,哪个线程被哪个线程池拒绝服务,throw一个异常
  2. ThreadPoolExector.DiscardPolicy:抛弃但不抛出异常,不告诉你直接扔了,空方法
  3. ThreadPoolExector.DiscardOldestPolicy:将队列中等待最久的任务扔掉,新来的请求放入队列中排队等待,只要线程池没有shutdown,那么线程池.getQueue().poll(),即获取阻塞队列并出队列第一个元素,然后执行新线程,线程池.execute(新线程)
  4. ThreadPoolExecutor.CallerRunsPolicy:如果被拒绝了,调用的线程直接自己执行该线程,只要线程池没有shutdown新线程.run()

阻塞队列的实现类

  1. ArrayBlockingQueue(不怎么用)
  2. LinkedBlockingQueue
  3. SynchronousQueue
    在这里插入图片描述
    一个特殊的队列,只能够容纳一个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。
  4. PriorityBlockingQueue(不怎么用)

线程池的状态

volatile int runState; 轻量级的锁,保证了立即可见性

  1. RUNNING :线程池处于创建之后的状态,正常状态,可以接受新的任务,处理任务 running-》shutdown/stop
  2. SHUTDOWN :调用shutdown方法后,拒绝接受新请求,处理已有线程执行 shutdown-》stop/tidying
  3. STOP :调用shutdownNow方法之后,拒绝接受新请求,尝试终止正在执行的线程,并清空排队队列 stop-》tidying
  4. TIDYING :没有线程在执行,队列里也没有线程请求排队 tidying-》terminated
  5. TERMINATED :没有线程正在执行,队列里也没有线程请求排队,调用terminated方法

在这里插入图片描述

execute方法源码
在这里插入图片描述

  • 如果请求线程为空,抛出空指针异常

  • 否则,判断当先线程数是否大于核心池大小:小于就放入核心池,大于等于就去排队

  • 为什么放入核心池还能失败呢?那是因为execute没有加锁,此时可能有其它线程申请成空使得大于等于核心池大小或者调用shutdown方法使得无法接受新线程请求

  • 放入核心池:加锁,再次判断是否大于等于核心池大小以及线程池状态(判断或者修改线程池状态必须加锁),如果小于且状态为running就将command传给一个线程addThread过程(尝试用线程工厂分配或者创建线程的),解锁(一定放在finally中,否则会死锁),然后start()线程同时返回true,否则返回false
    在这里插入图片描述
    在这里插入图片描述

  • 核心池放入失败也要排队

  • 排队:判断是否是running状态,是的话就offer即入队列,都成功则等待执行;如果状态或者入队失败,那么就放入最大池中,如果放入最大池也失败则采取拒绝策略

  • 如果等待过程中状态变了或者无线程运行了,则调用一个确保线程一定执行的方法ensureQueuedTaskHandled(command)

设置核心池大小——一般需要根据任务的类型来配置线程池大小

  • 如果是CPU密集型任务,就需要尽量压榨CPU,参考值可以设为 NCPU+1
  • 如果是IO密集型任务,参考值可以设置为2*NCPU
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值