Java线程池,10分钟搞定!

共 6998字,需浏览 14分钟

 ·

2022-06-30 21:10

大家好我是鱼皮,今天分享 Java线程池相关知识点。

大家都知道找Java工作的时候,线程池是一个必问的知识点,面试时,有的人只能讲五分钟,而有些人可以讲半个小时,差别在哪?在于知识的深度。下面几个面试高频题,你会吗?

  • 线程池各个参数的作用,简单阐述一下线程池工作流程。
  • 常见的线程池有哪些,分别适用于什么场景?
  • 使用无界队列的线程会导致内存飙升吗?

以下文章来源:峰哥聊编程

Java线程池概念

顾名思义,管理线程的池子,相比于手工创建、运行线程,使用线程池,有如下优点

  • 降低线程创建和销毁线程造成的开销
  • 提高响应速度。任务到达时,相对于手工创建一个线程,直接从线程池中拿线程,速度肯定快很多
  • 提高线程可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配、调优和监控

Java线程池创建

无论是创建何种类型线程池(FixedThreadPool、CachedThreadPool…),均会调用ThreadPoolExecutor构造函数,下面详细解读各个参数的作用

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)
 
{
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

线程池执行流程

当提交一个新任务,线程池的处理流程如下:

  • 判断线程池中核心线程数是否已达阈值corePoolSize,若否,则创建一个新核心线程执行任务

  • 若核心线程数已达阈值corePoolSize,判断阻塞队列workQueue是否已满,若未满,则将新任务添加进阻塞队列

  • 若满,再判断,线程池中线程数是否达到阈值maximumPoolSize,若否,则新建一个非核心线程执行任务。若达到阈值,则执行线程池饱和策略。

线程池饱和策略分为一下几种:

  • AbortPolicy:直接抛出一个异常,默认策略
  • DiscardPolicy: 直接丢弃任务
  • DiscardOldestPolicy:抛弃下一个将要被执行的任务(最旧任务)
  • CallerRunsPolicy:主线程中执行任务

从流程角度,更形象的图:

从结构角度,更形象的图:

几种常用的线程池

几种典型的工作队列

  • ArrayBlockingQueue:使用数组实现的有界阻塞队列,特性先进先出
  • LinkedBlockingQueue:使用链表实现的阻塞队列,特性先进先出,可以设置其容量,默认为Interger.MAX_VALUE,特性先进先出
  • PriorityBlockingQueue:使用平衡二叉树堆,实现的具有优先级的无界阻塞队列
  • DelayQueue:无界阻塞延迟队列,队列中每个元素均有过期时间,当从队列获取元素时,只有
  • 过期元素才会出队列。队列头元素是最块要过期的元素。
  • SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作,必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态

几种典型的线程池

SingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(11,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

创建单个线程。它适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景。

SingleThreadExecutor的corePoolSize和maximumPoolSize被设置为1,使用无界队列LinkedBlockingQueue作为线程池的工作队列。

  • 当线程池中没有线程时,会创建一个新线程来执行任务。
  • 当前线程池中有一个线程后,将新任务加入LinkedBlockingQueue
  • 线程执行完第一个任务后,会在一个无限循环中反复从LinkedBlockingQueue 获取任务来执行。

使用场景:适用于串行执行任务场景

FixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}

corePoolSize等于maximumPoolSize,所以线程池中只有核心线程,使用无界阻塞队列LinkedBlockingQueue作为工作队列。

FixedThreadPool是一种线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭。当所有的线程都处于活动状态时,新的任务都会处于等待状态,直到有线程空闲出来。

  • 如果当前运行的线程数少于corePoolSize,则创建新线程来执行任务。
  • 在线程数目达到corePoolSize后,将新任务放到LinkedBlockingQueue阻塞队列中。
  • 线程执行完(1)中任务后,会在循环中反复从LinkedBlockingQueue获取任务来执行。

使用场景:适用于处理CPU密集型的任务,确保CPU在长期被工作线程使用的情况下,尽可能的少的分配线程,即适用执行长期的任务。

CachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

核心线程数为0,总线程数量阈值为Integer.MAX_VALUE,即可以创建无限的非核心线程。最新 Java 面试题出炉!看看吧。

执行流程

流程形象图

结构形象图

使用场景:执行大量短生命周期任务。因为maximumPoolSize是无界的,所以提交任务的速度 > 线程池中线程处理任务的速度就要不断创建新线程;每次提交任务,都会立即有线程去处理,因此CachedThreadPool适用于处理大量、耗时少的任务。

ScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          new DelayedWorkQueue());
}

线程总数阈值为Integer.MAX_VALUE,工作队列使用DelayedWorkQueue,非核心线程存活时间为0,所以线程池仅仅包含固定数目的核心线程。

两种方式提交任务:

  • scheduleAtFixedRate: 按照固定速率周期执行
  • scheduleWithFixedDelay:上个任务延迟固定时间后执行

使用场景:周期性执行任务,并且需要限制线程数量的场景

面试题:使用无界队列的线程池会导致内存飙升吗?

答案 :会的,newFixedThreadPool使用了无界的阻塞队列LinkedBlockingQueue,如果线程获取一个任务后,任务的执行时间比较长,会导致队列的任务越积越多,导致机器内存使用不停飙升, 最终导致OOM。



以上就是本期分享。

最后,欢迎加入 鱼皮的编程知识星球(点击了解详情),和 8700 多名小伙伴们一起交流学习,向鱼皮和大厂同学 1 对 1 提问、帮你制定学习计划不迷茫、跟着鱼皮直播做项目(往期项目可无限回看)领取鱼皮原创编程学习/求职资料等。


往期推荐

编程导航,火了!

错失字节跳动数亿身价!我有话说

没有操作系统,程序还能运行吗?

全栈初中生,牛皮!

这些JS题面试时一定要答对

浏览 55
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报