1.什么是线程池?

线程池就是提前创建若干个线程,
如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。减少频繁创建和销毁线程消耗系统资源。

2.为什么要用线程池 ?

频繁创建、销毁 线程,将是对系统资源的极大浪费。
因此,实际开发中我们将使用线程池来管理、复用线程。
使用线程池,可以
1.降低资源消耗: 重复利用线程,减少创建和销毁造成的消耗。
2.提升响应速度: 任务到达,不需要创建,立即执行。
3.提高可管理型: 线程是CPU调度和分派的基本单位,
如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性。
使用线程池可以统一进行 分配、调优和监控。

3.创建线程池的方式?

Java从1.5 Executors 类提供四种创建线程池方式
1).newSingleThreadExecutor()

  • 单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务,(队列长度-Integer.MAX_VALUE)

2).newFixedThreadPool(maximumPoolSize)

  • **定最大线程数,线程池 **,核心线程数=最大线程数=设置的线程数,可控制线程最大并发数,(队列长度-Integer.MAX_VALUE);

3).newCachedThreadPool()

  • 可缓存线程池,有任务才新建线程,闲置线程保存60秒 ,(最大线程数长度-Integer.MAX_VALUE)

4).newScheduledThreadPool(corePoolSize)

  • 定核心线程数,线程池,支持定时及周期性任务执行。(最大线程数长度-Integer.MAX_VALUE)

4.为什么不建议使用 Executors静态工厂构建线程池

主要原因:队列堆积,有OOM风险
1.FixedThreadPool 和 SingleThreadPool
允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM

2.CachedThreadPool 和 ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

5.线程池参数

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) { }
corePoolSize:核心线程数量,一直存在,除非 allowCoreThreadTimeOut设置为true
maximumPoolSize:线程池允许的最大线程池数量 10
keepAliveTime:线程数量超过corePoolSize,空闲线程的最大超时时间
unit:超时时间的单位
workQueue:工作队列,保存未执行的Runnable 任务 (BlockingQueue 的实现类)
threadFactory:创建线程的工厂类
handler:当线程已满,工作队列也满了的时候,会被调用。被用来实现各种拒绝策略。

工作队列介绍

线程池创建线程 JAVA 线程池创建线程不销毁_线程池创建线程 JAVA

6.线程池的执行顺序

线程池创建线程 JAVA 线程池创建线程不销毁_线程池_02

当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运
行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它
最终会收缩到 corePoolSize 的大小。

7.四种策略**

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常
  • ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:**丢弃队列最老的任务,然后重新提交被拒绝的任务 **
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

8.实现原理

线程池由两个核心数据结构组成:
1)线程集合(workers):存放执行任务的线程,是一个HashSet;
2)任务等待队列(workQueue):存放等待线程池调度执行的任务,是一个阻塞式队列BlockingQueue;

8.如何合理设置线程池大小

任务一般可分为:CPU密集型、IO密集型、混合型,对于不同类型的任务需要分配不同大小的线程池。

  • CPU密集型任务: 尽量使用较小的线程池,一般为CPU核心数+1 ; (+1是利用等待空闲)
  • 因为CPU密集型任务使得CPU使用率很高,若开过多的线程数,只能增加上下文切换的次数,因此会带来额外的开销。
  • IO密集型任务: 一般为2*CPU核心数。
  • IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
  • 混合型任务
  • 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。

线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。
最佳线程数目 = (线程等待时间与线程CPU时间之比 + 1)* CPU数目

//java 中获取核心数 int availableProcessors = Runtime.getRuntime().availableProcessors();

9.线程池的动态规划参数动态化

JDK原生线程池ThreadPoolExecutor提供了如下几个public的setter方法,如下图所示:

线程池创建线程 JAVA 线程池创建线程不销毁_线程池_03


JDK允许线程池使用方通过ThreadPoolExecutor的实例来动态设置线程池的核心策略,

以setCorePoolSize为方法例,

在运行期线程池使用方调用此方法设置corePoolSize之后,

线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。

对于当前值小于当前工作线程数的情况,说明有多余的worker线程,

此时会向当前空闲的worker线程发起中断请求以实现回收,

多余的worker在下次空闲的时候也会被回收;

对于当前值大于原始值且当前队列中有待执行任务,

则线程池会创建新的worker线程来执行队列任务,setCorePoolSize具体流程如下:

线程池创建线程 JAVA 线程池创建线程不销毁_系统资源_04