Moosphan/Android-Daily-Interview

2019-07-25:什么是线程池?如何创建一个线程池?

MoJieBlog opened this issue · 9 comments

2019-07-25:什么是线程池?如何创建一个线程池?

竟然没人回答?

线程池,顾名思义一堆线程在一起,核心成员是线程,线程是干什么的?当然是执行任务的!而线程池呢?当然也是!
而用线程池有什么好处呢?举个例子,你是一家公司的老板(进程),这家公司接到了N个任务,

  • 假如不使用线程池:
    你每个任务都新建一个线程去处理,就相当于每个工作都招一个人来做(创建线程),做完直接开除掉(线程销毁),这显然有问题;
  • 使用线程池:
    你手底下有或没有一些空闲的员工(执行完任务待命的线程),当任务来临时,先看看自己手底下有没有没事做的员工(空闲线程),有的话直接把任务交出去;没有的话你可以让任务等会执行(任务排队),
    等待有线程空闲,或者你可以多招点人来处理(创建新线程),或者拒绝执行这个任务;当有人空闲时你可以让他等待新的任务来临,等待太长的话你可以把他开掉(线程销毁)

可见,使用线程池可以实现线程复用,以节省创建线程的所需的资源;
还有一种fork-join式线程池(任务抢占式线程池),适用于可分割的任务,大致实现和普通线程池差不多,但是其任务可以“抢占”:
比如之前很火的数一亿颗米作业,分成N个线程数,可能因为各种原因造成某个线程早早的就数完了属于这个线程任务的那部分,而其他线程可能还有一大堆的米没有数,

  • 使用普通线程池的话,这个线程就只是默默的看着,不会去继续帮忙;
  • fork-join式线程池,这个线程的任务执行完毕后还会帮忙一起数;

至于怎么创建线程池我就不说了,百度都有的

竟然没人回答?

线程池,顾名思义核心成员是线程,线程是干什么的?当然是执行任务的!而线程池呢?当然也是!
而用线程池有什么好处呢?举个例子,你是一家公司的老板(进程),这家公司接到了N个任务,

  • 假如不使用线程池:
    你每个任务都新建一个线程去处理,就相当于每个工作都招一个人来做(创建线程),做完直接开除掉(线程销毁),这显然有问题;
  • 使用线程池:
    你手底下有或没有一些空闲的员工(执行完任务待命的线程),当任务来临时,先看看自己手底下有没有没事做的员工(空闲线程),有的话直接把任务交出去;没有的话你可以让任务等会执行(任务排队),
    等待有线程空闲,或者你可以多招点人来处理(创建新线程),或者拒绝执行这个任务;当有人空闲时你可以让他等待新的任务来临,等待太长的话你可以把他开掉(线程销毁)

可见,使用线程池可以实现线程复用,以节省创建线程的所需的资源;
还有一种fork-join式线程池(任务抢占式线程池),适用于可分割的任务,大致实现和普通线程池差不多,但是其任务可以“抢占”:
比如之前很火的数一亿颗米作业,分成N个线程数,可能因为各种原因造成某个线程早早的就数完了属于这个线程任务的那部分,而其他线程可能还有一大堆的米没有数,

  • 使用普通线程池的话,这个线程就只是默默的看着,不会去继续帮忙;
  • fork-join式线程池,这个线程的任务执行完毕后还会帮忙一起数;

至于怎么创建线程池我就不说了,百度都有的

666,通俗易懂。

线程池可以通过 ,Executors.方法创建 一些android为我们提供的线程池。比如 Executors.newFixedThreadPool(int)

但是阿里霸霸提醒我们说,
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
   允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
  允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

让我们用这种方法创建自己的线程池new ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, //保活时间
TimeUnit unit, //时间单位
BlockingQueue workQueue, //任务队列
RejectedExecutionHandler handler) //最后一个参数, 当前线程数超过。 任务队列数+最大线程数时的具体操作。比如抛出异常或者丢掉线程任务等等。

    再有就是其他具体细节问题了。比如shutdown(),和shutdownNow()区别啦。看名字应该都懂,一个等待 队列里 没完事的继续执行再关,另一个直接关取消任务。
     
    再有就是ScheduledThreadPool这种用于执行周期性任务的。scheduleAtFixedRate方法和scheduleWithFixedDelay方法区别啦?

     任务耗时  小于   线程周期时间  scheduleAtFixedRate(勤快) 在周期时间到达时,开始新一轮任务。 scheduleWithFixedDelay(懒惰)在任务结束后,才开始计算周期时间,周期时间结束后,再开始新的任务。 

    任务耗时  大于  线程周期时间   scheduleAtFixedRate(勤快) 任务结束后,马上开始新一轮任务。%%但是由于 任务耗时 > 线程周期时间%% 实际上这个周期时间已经被耗时时间影响到了。
    scheduleWithFixedDelay (懒惰)任务结束后,开始计算周期时间,然后开始执行新一轮任务。

    ps:就好比耗时的任务是每天上班。 线程周期24h(每天11点来git答题)。
    scheduleAtFixedRate(勤快,下班了,马上答题。提前下班。11点答题。加班超过11点,下班了立刻答题)
    scheduleWithFixedDelay(懒惰,必须休息。即下班了,必须休息固定的时间,在去答题。无论早下班还是晚下班。)

线程池是用来存放线程的池子,里面的线程可以被重复利用,不浪费。
创建线程池:用Executors 。
singleThreadPool单一线程
fixedThreadPool固定数量的线程池
scheduleThreadPool周期任务
cacheThreadPool想建几个就建几个

这道题, 我给一点面试总结

处理任务的优先级为: 核心线程 corePoolSize, 阻塞队列 workQueue, 最大线程 maximumPoolSize;
如果三者都满了, 使用 handler 处理被拒绝的任务;
1.. 如果线程数量 <= 核心线程数, 那么直接创建新的线程, 来执行任务, 不会放入阻塞队列中;
核心线程在 allowCoreThreadTimeout 为true 时, 会超时退出, 默认情况下不会退出;
2.. 如果线程数量 > 核心线程数, 但是 <= 最大线程数, 并且阻塞队列是 LinkedBlockingDeque 的时候, 超过核心线程数量, 的任务, 会放在阻塞队列中排队;
3.. 如果线程数量 > 核心线程数, 但是 <= 最大线程数, 并且阻塞队列是 SynchronousQueue 的时候, 线程池会创建, 新的线程, 来执行任务, 这些任务也不会被放在阻塞队列中;
这些线程属于非核心线程, 在任务完成后, 闲置时间, 达到了超时时间, 就会被清除;
4.. 如果线程数量 > 核心线程数, 并且 > 最大线程数, 当阻塞队列是 LinkedBlockingDeque, 会将超过核心线程数量, 的任务, 放在阻塞队列中排队;
当阻塞队列 LinkedBlockingDeque 没有大小限制时, 线程池的最大线程数设置是无效的, 他的线程数最多不会超过核心线程数;
当阻塞队列 LinkedBlockingDeque 有大小限制时, 当阻塞队列塞满时, 新增的任务, 会直接创建新的线程, 来执行任务, 当创建的线程数量超过, 最大线程数量时会抛异常;
5.. 如果线程数量 > 核心线程数, 并且 > 最大线程数, 当阻塞队列是 SynchronousQueue 时, 会因为线程池拒绝添加任务而抛出异常;

https://github.com/Alex-Cin/Document/blob/b233cab0bd6f3e817ce373a27e656590cb42b463/Java/basic/concurrent/tpe_mechanism.md

这道题, 我给一点面试总结

处理任务的优先级为: 核心线程 corePoolSize, 阻塞队列 workQueue, 最大线程 maximumPoolSize;
如果三者都满了, 使用 handler 处理被拒绝的任务;
1.. 如果线程数量 <= 核心线程数, 那么直接创建新的线程, 来执行任务, 不会放入阻塞队列中;
核心线程在 allowCoreThreadTimeout 为true 时, 会超时退出, 默认情况下不会退出;
2.. 如果线程数量 > 核心线程数, 但是 <= 最大线程数, 并且阻塞队列是 LinkedBlockingDeque 的时候, 超过核心线程数量, 的任务, 会放在阻塞队列中排队;
3.. 如果线程数量 > 核心线程数, 但是 <= 最大线程数, 并且阻塞队列是 SynchronousQueue 的时候, 线程池会创建, 新的线程, 来执行任务, 这些任务也不会被放在阻塞队列中;
这些线程属于非核心线程, 在任务完成后, 闲置时间, 达到了超时时间, 就会被清除;
4.. 如果线程数量 > 核心线程数, 并且 > 最大线程数, 当阻塞队列是 LinkedBlockingDeque, 会将超过核心线程数量, 的任务, 放在阻塞队列中排队;
当阻塞队列 LinkedBlockingDeque 没有大小限制时, 线程池的最大线程数设置是无效的, 他的线程数最多不会超过核心线程数;
当阻塞队列 LinkedBlockingDeque 有大小限制时, 当阻塞队列塞满时, 新增的任务, 会直接创建新的线程, 来执行任务, 当创建的线程数量超过, 最大线程数量时会抛异常;
5.. 如果线程数量 > 核心线程数, 并且 > 最大线程数, 当阻塞队列是 SynchronousQueue 时, 会因为线程池拒绝添加任务而抛出异常;

https://github.com/Alex-Cin/Document/blob/b233cab0bd6f3e817ce373a27e656590cb42b463/Java/basic/concurrent/tpe_mechanism.md

这位老哥每次的回答都有点深入的啊,学到了

IT666 commented

线程池:
1.线程池:创建多个线程,并管理线程,为线程分配任务并执行。
2.使用线程池的好处:多个线程的创建会占用过多的系统资源,造成死锁或OOM
3.线程池的作用:(1)可以复用创建好的线程,减少线程的创建或销毁的开销;(2)提高响应速度,当任务到达时,不需要等待就可以立即执行;(3)可有效控制最大并发的线程数,提高系统资源的利用率。防止死锁或OOM;(4)可以提供定时和定期的执行方式。
4.线程池参数:corePoolSize(核心线程数)、maximumPoolSize(最大线程数)、workQueue(阻塞队列)、keepAliveTime(保活时间)、threadFactory(线程工厂,用于生成线程)。
5.线程池提交任务:有两个方法可以向线程池提交任务,分别是execute()和submit()。
execute():用于提交不需要返回值的任务,无法判断任务是否被线程执行成功。
submit():用于提交需要返回值的任务,会返回一个future类型的对象,来判断任务是否执行成功,还可以通过future的get()方法获取返回值,get()方法会阻塞当前线程直到任务完成。
5.线程池的工作流程:
(1)有新任务时,判断当前线程数是否超过corePoolSize,如果小于corePoolSize,即使有空闲线程可以执行任务,也会创建一个新的线程用来执行该任务;
(2)如果超过corePoolSize,就把任务放在workQueue(阻塞队列)中,等待被执行,前提是workQueue是有界队列;
(3)如果workQueue满了,判断当前线程数是否小于maximumPoolSize,如果小于maximumPoolSize就创建一个线程用来执行任务。
(4)如果当前线程数大于maximumPoolSize,就会执行线程池的饱和策略。
6.线程池的饱和策略:(1)默认策略:直接抛出异常;(2)用调用者所在的线程(提交任务的那个线程)执行任务;(3)丢弃阻塞队列中最靠前的任务,执行当前任务;(4)直接丢弃任务。
7.线程池的状态:
(1)RUNNING:接收提交的任务。
(2)SHUTDOWN:不再接收新提交的任务,继续处理阻塞队列中的任务。
(3)STOP:不再接收新的任务,也不会处理阻塞队列中的任务,并会终止正在执行的任务。
(4)TIDYING:所有的任务已终止,ctl记录的任务数量为0,会执行钩子函数terminated()。
(5)TERMINATED:线程池彻底终止。
8.关闭线程池的方法:ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow()。
原理:都是循环遍历线程池的工作线程,然后依次调用线程的intercept()方法来中断线程。
shutdown():将线程池状态设置为SHUTDOWN。
shutdownNow():将线程池状态设置为STOP。

使用ThreadPoolExecutor类进行线程池初始化
线程池的构造函数 主要参数有
核心线程数 总线程数 非核心线程超时时间, 超时单位, 工作队列 线程工厂 异常处理者

其中核心线程创建之后就不会被系统回收,需要用户主动回收
非核心线程在闲置超过超时时间之后会被系统自动回收

一个工作发起的时候,会先去用核心线程处理,如果能处理,就处理,如果满了 则进入工作队列排队,
如果工作队列也满了,就会去生成非核心线程去处理,如果工作队列也满了,那就会抛出异常.

系统自带四种线程池
singleThreadPool 只有一个核心线程,工作队列长队 integer.max
fixedThreadPool 只有核心线程且数量固定,工作队列max
cacheThreadPool 只有非核心线程,且数量为max
scheduleThreadPool 核心数固定 非核心max
根据阿里巴巴java开发手册 这四种都有可能导致oom
因为 工作队列无限长 可能挤压过多任务,oom
非核心数无限多,也可能启动过多线程oom

所以要根据需求去初始化适合的线程池

线程池:顾名思义,一堆线程的组合,且内部包含线程池管理器,线程任务调度接口和任务队列。以空间换时间,避免频繁创建线程浪费资源。线程池的创建分为很多种

  • newCacheThreadPool 带缓存的线程池,只有非核心
  • newFixedThreadPool(coreSize) 核心线程数
  • newSingleThreadPool 单核心线程数,工作队列长度为integer.max
  • scheduleThreadPool 核心数固定,非核心max