线程池的调用原理
线程池的七大参数:
核心线程数、最大线程数、任务队列、拒绝策略、闲置时间、时间单位、线程工厂
任务进入线程池后线程池的执行顺序:
- 核心线程(用完)---处理完一个任务后会取出任务队列中的第一个任务来执行
- 任务队列(装满)
- 普通线程(用完)
- 拒绝策略
深入线程池
ExecutorService pool = Executors.newSingleThreadExecutor();
ExecutorService pool = Executors.newFixedThreadPool(3);
ExecutorService pool = Executors.newCachedThreadPool();
三种线程池底层都是ThreadPoolExecutor类的对象
-- 分析ThreadPoolExecutor类的构造方法源码--------------------------------
public ThreadPoolExecutor(
int corePoolSize, ------------- 核心线程数量
int maximumPoolSize, ------------- 最大线程数量
long keepAliveTime, ------------- 闲置时间,作用于核心线程数与最大线程数之间的线程
TimeUnit unit, ------------- keepAliveTime的时间单位(可以是毫秒、秒....)
BlockingQueue<Runnable> workQueue, -- 任务队列
ThreadFactory threadFactory, -------- 线程工厂
RejectedExecutionHandler handler ---- 达到了线程界限和队列容量时的处理方案(拒绝策略)
) {}
执行步骤:
1.创建线程池后
2.任务提交后,查看是否有核心线程:
3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
3.2 有 -> 查看是否有闲置核心线程:
4.1 有 -> 执行任务 -> 执行完毕后又回到线程池
4.2 没有 -> 查看当前核心线程数是否核心线程数量:
5.1 否 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中
5.2 是 -> 查看任务列表是否装载满:
6.1 没有 -> 就放入列表中,等待出现闲置线程
6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程)
7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中
7.2 有 -> 查看是否有闲置普通线程
7.1.1 有 -> 执行任务 -> 执行完毕后又回到线程池中
7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:
8.1 是 -> 执行处理方案(默认处理抛出异常)
8.2 否 ->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中
注:
1.为了更好的理解,在这里区分核心线程和普通线程,实际上区分的这么清楚,都是线程
2.默认的处理方案就是抛出RejectedExecutionException
总结:核心线程满载 -> 任务队列 -> 普通线程
-- 分析单个线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newSingleThreadExecutor();
new ThreadPoolExecutor(
1, -- 核心线程数量
1, -- 最大线程数量
0L, -- 闲置时间
TimeUnit.MILLISECONDS, -- 时间单位(毫秒)
new LinkedBlockingQueue<Runnable>() -- 无界任务队列,可以无限添加任务
)
-- 分析指定线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newFixedThreadPool(3);
new ThreadPoolExecutor(
nThreads, -- 核心线程数量
nThreads, -- 最大线程数量
0L, -- 闲置时间
TimeUnit.MILLISECONDS, -- 时间单位(毫秒)
new LinkedBlockingQueue<Runnable>()-- 无界任务队列,可以无限添加任务
)
-- 创建可缓存线程的线程池 -----------------------------------
new ThreadPoolExecutor(
0, -- 核心线程数量
Integer.MAX_VALUE,-- 最大线程数量
60L, -- 闲置时间
TimeUnit.SECONDS, -- 时间单位(秒)
new SynchronousQueue<Runnable>() -- 直接提交队列(同步队列):没有容量队列
//线程数目不可控
Java自带的线程池底层使用用无界队列,都存在内存溢出的风险
任务队列
队列名称 | 详解 |
LinkedBlockingQueue无界任务队列 | 使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题 |
SynchronousQueue 同步任务队列 直接提交任务队列 | 使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。 任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略; |
ArrayBlockingQueue有界任务队列 | 使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。 |
PriorityBlockingQueue优先任务队列 | 使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。 |