• Java线程池是个什么东西?核心原理又是什么?


    如果觉得我的文章还不错的话就点个赞,关注一波,转发收藏吧,另外可以微信搜索【佘凡架构师】阅读更多的好文章,获取我为大家准备的资料。

    背景

    最近有好多同学都在问,面试官为什么那么喜欢问线程池,blabla连环炮一大堆,我们应该怎么应对面试话的连环夺命追击问?

    我们知道但凡是一个系统,那么就不会让这个系统无限制的创建很多很多的线程,那么分分钟就OOM,肯定会构建一个线程池,有一定数量的线程,让他们执行各种各样的任务,线程执行完毕任务之后,不会让线程销毁掉自己,继续去执行下一个任务。这样避免线程的创建,初始化,销毁的各种系统资源成本。

    什么是线程池?

    简单来讲线程池就是一个池子,里面装有多个线程。有点像我们生活中的游泳池,首先我们会给游泳池的长宽高设置好体积,然后当我们要游泳的时候就往里面注入水。当我们游泳完毕后,水不要了就会做释放。

    那线程池也是如此,当我们有任务需要的时候,这样就会生成核心线程处理任务。
    那么问题来了,我们的任务太多,核心线程处理不过来怎么办?不用担心,我们有阻塞队列,将任务放入阻塞队列中,等待处理。
    那问题又来了,阻塞队列都已经放满了任务,这个时候怎么办?没关系,我们继续生成非核心线程去处理阻塞队列中的任务。
    完了完了,连非核心线程都被使用完毕,这个时候应该怎么办?没关系,我们还有终极大招,拒绝策略。

    让我们画个图理解一下吧

    1. 首先我们大池子(线程池),这个线程池里默认只能装3个线程。现在里面是空的,因为没有任务可以执行,所以核心线程还没初始化。
      在这里插入图片描述

    2. 现在进来了3个任务,所以创建了3个核心线程去执行任务
      在这里插入图片描述

    3. 这个时候突然又出现了3个任务,但是线程池中的核心线程已经满了,所以没办法,只能放入阻塞队列。
      在这里插入图片描述

    4. 当任务1执行完毕以后,任务4就会被现在空闲的核心线程a继续执行在这里插入图片描述

    5. 这个时候突然又来了3个任务(任务7,任务8,任务9),而阻塞队列中只能放3个任务。并且线程池中的核心线程都还没有执行手头的任务。这个时候我们有非核心线程(假设2个),就会创建非核心线程来处理剩余的任务(处理任务8,任务9)。在这里插入图片描述

    6. 假如又有了2个任务(任务10,任务11),这个时候所有的线程还是没有把手头的任务执行完毕。我们就会执行拒绝策略在这里插入图片描述

    7. 恰好这个时候突然执行力度增强,一下子线程池中的任务全部执行完毕。那么线程池中的线程会从阻塞队列中取出任务来执行。在这里插入图片描述

    8. 之后再也没有任务进入到阻塞队列中,那么非核心线程会开始等待空闲时间。一旦超过空闲时间,那么非核心线程就会自动销毁。最终只有3个核心线程存在在这里插入图片描述

    线程池的核心参数有哪些?

    从上面的那几个图我们了解到,线程池有核心线程,非核心线程,阻塞队列,非核心线程的空闲时间,拒绝策略。那让我们来看一下线程池的核心参数代码吧。

    public ThreadPoolExecutor(int corePoolSize, //核心线程数
                                  int maximumPoolSize,//最大线程数=核心线程数+非核心线程数
                                  long keepAliveTime,//空闲时间(非核心线程无任务空闲时间)
                                  TimeUnit unit,//空闲单位
                                  BlockingQueue<Runnable> workQueue //阻塞队列
                                  ) { 
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory() //默认线程工厂
                 , defaultHandler //默认拒绝策略(AbortPolicy)
                 );
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    线程池的拒绝策略有哪些?

    在这里插入图片描述
    CallerRunsPolicy : 直接调用当前线程执行任务策略
    AbortPolicy :直接抛异常
    DiscardPolicy:丢弃当前任务策略
    DiscardOldestPolicy:丢掉最后一个阻塞队列中的任务,尝试把当前任务放进去
    还有一种自定义策略,实现RejectedExecutionHandler接口。一般我们都是使用这种策略,保证任务的不丢失。

    线程池有哪几种创建方式?

    1. 使用Executors,Executors是Java的一个工具类,其中有4种创建线程的方式。

    newFixedThreadPool(固定线程池):Executors.newFixedThreadPool(nThreads)
    缺点:LinkedBlockingQueue是无界队列,当任务太多,而线程太少,任务处理不过来的时候。太多的任务放在无界队列种容易导致OOM。

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    newSingleThreadExecutor(单个线程池):Executors.newSingleThreadExecutor()。核心线程与最大线程都是1
    缺点:LinkedBlockingQueue是无界队列,当任务太多,而线程太少,任务处理不过来的时候。太多的任务放在无界队列种容易导致OOM。

    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    newCachedThreadPool(缓存线程池):Executors.newCachedThreadPool()
    缺点:最大线程数为Integer.MAX_VALUE,当任务过多,而阻塞队列SynchronousQueue容量为0。创建太多的非核心线程,导致OOM。

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

    ScheduledThreadPoolExecutor(定时线程池):Executors.ScheduledThreadPoolExecutor(corePoolSize)
    缺点:自定义了核心线程数,但是最大线程数依旧为Integer.MAX_VALUE。

    public ScheduledThreadPoolExecutor(int corePoolSize) {
       super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
    
    • 1
    • 2
    • 3
    • 4
    1. 自定义核心参数,创建线程池
      在这里插入图片描述

    生产环境突然宕机,线程池中的请求任务怎么办?

    一般正常情况就是生产环境突然宕机,线程池中的积压的请求任务当即立即全部丢失,导致线上数据不一致情况。那我们应该怎么解决上述的这个问题?解决方案就是将任务提交到线程池去执行任务时,先在库表里新增一条这样的数据,并且把状态改为未完成。等任务执行完毕以后同步更新库表信息为已完成。使用这样的方式来保证就算生产环境宕机,我们也能够从库表的数据进行回溯。

    生产环境使用无界队列会产生什么问题?

    生产环境使用无界队列时,若请求任务数不断的增大,会使得当前的阻塞无界队列中不断堆加新的请求任务,直接让内存飙升。更有甚者,直接OOM。

    生产环境中队列满了,最大线程数为Integer.MAX_VALUE,会出现什么问题?

    同上,生产环境中的阻塞队列满了以后,会不断的创建新的非核心线程来处理任务。它的好处就是当请求任务一下飙升堆积过来之后,能够创建大量的线程快速处理,对并发的请求任务进行削峰处理。缺点就是一旦创建了大量的线程,会使得CPU和内存同时飙升。大量的线程在执行任务时会占用CPU的资源,大量的线程创建会占用内存资源。

  • 相关阅读:
    Springboot整合Mybatis
    关于Selemium那些事
    2020蓝桥杯国赛B组-搬砖-(贪心排序+01背包)
    Java 学习路线分享 maven 是什么?
    apache poi excel export
    基于图像字典学习的去噪技术研究与实践
    bahir-flink
    刷题心得 【2731. 移动机器人】
    Spring Cloud 之 Sentinel简介与GATEWAY整合实现
    线程的状态
  • 原文地址:https://blog.csdn.net/p874593785/article/details/125417785