多线程之ThreadPoolExecutor详解

一、为什么使用ThreadPoolExecutor来创建线程池

线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
因为线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写。因为Executors 返回的线程池对象的弊端如下:
Executors 返回的线程池方式:

  • newCachedThreadPool:创建一个可缓冲的线程,如果线程池大小超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool:创建一个固定大小的线程池,如果任务数量超过线程池大小,则将多余的任务放到队列中。
  • newScheduledThreadPool:创建一个固定大小,并且能执行定时周期任务的线程池。
  • newSingleThreadExecutor:创建只有一个线程的线程池,保证所有的任务安装顺序执行。
  • newWorkStealingPool:抢占式线程池,和前四种实现方式不一样,前四种是ThreadPoolExecutor实现,其由ForkJoinPool。

Executors 返回的线程池方式弊端:

  • 1) FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为
    Integer.MAX,堆积的请求处理队列可能会耗费非常大的内存,甚至OOM;
  • 2) CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为
    Integer.MAX_VALUE,可能会创建大量的线程,甚至OOM;

总体来说,线程池有如下的优势:

  • (1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • (2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • (3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行

二、ThreadPoolExecutor创建线程池参数详解

ThreadPoolExecutor创建线程池的参数详解:
corePoolSize核心线程数量(必须)、maximumPoolSize最大线程数量(必须)、keepAliveTime 、TimeUnit、queue、线程工厂、拒绝策略。
核心线程数量:线程池会维护最小线程数量,一般就算这些线程空闲也不会销毁,除非设置了allowCoreThreadTime
最大线程数量:当任务被提交到线程池,首先判断是否有空闲线程,若果有则直接交由处理,如果没有则放入队列中,如果队列也满了,
空闲线程存活时间:当有空闲线程时,而且当前线程数量超过核心线程数量,那么当前线程在一定时间后就会销毁;
空闲线程存活时间单位:指定 keepAliveTime 参数的时间单位,可以选择的枚举值为:TimeUnit.DAYS/
工作队列:四种常见队列ArrayBlockingQueue基于数组有界阻塞队列 LinkedBlockingDeque
线程工厂:用来设置线程池名称,线程是否为守护线程等信息;
拒绝策略:四种 AbortPolicy满之后直接拒绝,并且抛出异常 DiscardPolicy满之后直接拒绝 Disc;

事例

ExecutorService threadPool = new ThreadPoolExecutor(
    8, //corePoolSize线程池中核心线程数
    10, //maximumPoolSize 线程池中最大线程数
    60, //线程池中线程的最大空闲时间,超过这个时间空闲线程将被回收
    TimeUnit.SECONDS,//时间单位
    new ArrayBlockingQueue(500), //队列
    new ThreadPoolExecutor.CallerRunsPolicy()); //拒绝策略

三、线程池执行任务的顺序

线程池执行任务的顺序:

  • 线程数小于核心线程数时,创建线程

  • 线程数大于核心线程数,且任务队列未满时,将任务放入到任务队列中

  • 线程数大于核心线程数,且任务队列已经满了

      如果线程数小于最大线程数,创建线程
      若果线程数等于最大线程数,这个时候看拒绝策略使用的是那种了(有的直接拒绝任务、有的抛出异常在拒绝)
    

四、线程池核心线程数量的确定方案

线程池核心线程熟练地确定:数量的确定之CPU密集型和IO密集型:线程任务可以分为CPU密集型和IO密集型。(平时开发基本上都是IO密集型任务)

CPU密集型任务的特点是进行大量的计算,消耗CPU资源,比如计算圆周率、视频高清解码等。这种任务操作都是比较耗时间的操作。
IO密集型的任务的特点是涉及到网络(调用三方接口)、磁盘IO(文件操作)等。这类任务操作是CPU消耗很少,任务大部分时间。
刚才提到了,CPU密集型尽量配置少的线程,核心线程配置:CPU核数。而IO线程池应配置多的线程,核心线程配置:CPU核数 其中获取核数代码如下:int i = Runtime.getRuntime().availableProcessors