[基础篇]-Java线程池全介绍

1.什么是线程池

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

2.为什么要用线程池

频繁创建、销毁 线程。会对系统资源的极大浪费。如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性。因此,实际开发会使用线程池来管理、复用线程。

3.使用线程池的优点

  • 降低资源消耗: 重复利用线程,减少创建和销毁造成的消耗。
  • 提升响应速度: 任务到达,不需要创建,立即执行。
  • 提高可管理型: 线程是CPU调度和分派的基本单位,对任务统一进行 分配、调优和监控。

4.创建线程池的方式

Java从1.5 Executors 类提供四种创建线程池方式

4.1newSingleThreadExecutor

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

4.2newFixedThreadPool

  • 定线程数,线程池 ,核心线程数=最大线程数=设置的线程数,可控制线程最大并发数
  • 队列长度-Integer.MAX_VALUE
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
 return new ThreadPoolExecutor(nThreads, nThreads,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<Runnable>(),threadFactory);
}

4.3newCachedThreadPool

  • 可缓存线程池,有任务才新建线程,闲置线程保存60秒
  • 最大线程数长度-Integer.MAX_VALUE
 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

4.4newScheduledThreadPool

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

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

主要原因:队列堆积,有OOM风险

5.1.FixedThreadPool 和 SingleThreadPool

允许的请求队列(底层实现是LinkedBlockingQueue)队列长度为Integer.MAX_VALUE,
可能会堆积大量的请求,从而导致OOM

5.2.CachedThreadPool 和 ScheduledThreadPool

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

6.线程池参数与含义

  public ThreadPoolExecutor(  
                          int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
  }
参数 含义
corePoolSize 核心线程数量,一直存在
除非allowCoreThreadTimeOut设置为true
maximumPoolSize 线程池允许的最大线程池数量
keepAliveTime 线程数量超过corePoolSize,
空闲线程的最大超时时间
unit 超时时间的单位
workQueue 工作队列,保存等待执行任务的阻塞队列
threadFactory 自定义线程的工厂类,一般用来设置线程名称
handler 当线程池和队列都满了,再加入线程会执行此策略
详见第8点


附1.时间参数取值范围

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


附2.常见阻塞队列

  • ArrayBlockingQueue
    • 是一个基于数组结构的有界阻塞队列,按 FIFO(先进先出)原则对元素进行排序,创建时必须指定大小;
  • LinkedBlockingQueue
    • 基于链表的先进先出队列,若创建时没有指定队列大小,则默认为Integer.MAX_VALUE;
  • synchronousQueue
    • 是一个内部只能包含一个元素的队列。插入元素到队列的线程被阻塞,直到另一个线程从队列中获取了队列中存储的元素。同样,如果线程尝试获取元素并且当前不存在任何元素,则该线程将被阻塞,直到线程将元素插入队列

综上,一般建议使用LinkedBlockingQueue,并合理设置队列大小

7.线程池的执行顺序

[基础篇]-Java线程池全介绍_第1张图片

8.拒绝策略

 拒绝策略也可以叫饱和策略,作为线程池定义的最后一个参数,同样至关重要。线程数和队列不可能无穷大,当队列和线程都满了的时候,那么必须采取一种策略去处理继续追加的任务。于是就有了拒绝策略的引入。
以下是内置的4中拒绝策略,默认是ThreadPoolExecutor.AbortPolicy,当超过最大值时直接抛出异常
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy 丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy 丢弃队列最前面的任务,然后重新提交被拒绝的任务
ThreadPoolExecutor.CallerRunsPolicy 由调用线程(提交任务的线程)处理该任务

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

《Java并发编程实战》中最原始的公式是这样的:
  N = N * U * (1 + W/C)
其中 _N_是_CPU_核心数 , _U_是_CPU_使用率介于0~1之间 , W/C 是等待时间与计算时间的比率


java可以通过如下代码获取当前设备的CPU个数。

Runtime.getRuntime().availableProcessors()

实际使用中一般会根据任务类型划分,对于不同类型的任务适当分配不同大小的线程池

  • CPU密集型任务
    • 尽量使用较小的线程池,一般为CPU核心数+1
    • 因为CPU密集型任务使得CPU使用率很高,若开过多的线程,会增加上下文切换的次数,带来额外的开销。
  • IO密集型任务
    • 可以使用稍大的线程池,一般为2*CPU核心数。
    • IO密集型任务CPU使用率并不高,因此可以让CPU在等待IO的时候去处理别的任务,充分利用CPU时间。
  • 混合型任务
    • 可以将任务分成IO密集型和CPU密集型任务,然后分别用不同的线程池去处理。
      只要分完之后两个任务的执行时间相差不大,那么就会比串行执行来的高效。
    • 因为如果划分之后两个任务执行时间相差甚远,那么先执行完的任务就要等后执行完的任务,最终的时间仍然取决于后执行完的任务,而且还要加上任务拆分与合并的开销,得不偿失。


结论:线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程

10.线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭

  • shutdown()
    • 不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,将不会接受新的任务
    • 将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
  • shutdownNow()
    • 立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
    • 遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程
    • 先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

关注程序员小强公众号更多编程趣事,知识心得与您分享
在这里插入图片描述

你可能感兴趣的:(java基础)