【小威哥带你面试】第3期:java线程池万花筒

806 阅读5分钟

1:前言

爷青回!小威哥的博客又更新啦!!今天给大家总结一下万恶的面试官们一定会问的一个东西->线程池。😁



2:线程池问题点

2.1: 如何初始化一个线程池

  • 第一种方式(直接使用Executors类方法)
        ExecutorService pool2 = Executors.newFixedThreadPool(5);
        ExecutorService pool4 = Executors.newCachedThreadPool();
        ExecutorService pool3 = Executors.newSingleThreadExecutor();

这个时候同学们就会想:我的idea编译器不让我这么创建(其实是装了阿里的代码规范插件)。😏😏😏


没错!,这种方式创建的话会让我们根本不明白其中的参数含义,以及创建出来的东西是啥玩意,其实我们点进去代码就可以看到,不管创建什么线程池,其实都是创建同一个类ThreadPoolExecutor,无非是构造的参数不同。 如下图:

  • 😘 那么我们最好就是手动来创建一个线程池,而不是使用包装的方法。

2.2: 构造参数的含义

下面小威哥来用大白话解释下创建一个线程池的几个参数。

  • corePoolSize:核心线程数,可以理解为【正牌男友】,长年累月不停的帮你做事的线程😙
  • maximumPoolSize:最大线程数,可以理解为【备胎】,核心线程全部已经在工作的时候,并且等待队列爆满的时候,你(渣女)忍无可忍了,备胎线程们就会起来帮你执行任务。
  • keepAliveTime:最大线程保持时间,备胎线程如果启动后空了很久,超过这个时间了就会抛弃这些备胎,销毁线程。
  • workQueue:等待队列,当核心线程都处于工作状态没办法接受任务的时候,会把任务先放进这个队列进行缓冲,当这个任务队列满了之后就会开始启动【备胎线程】去执行任务。

其实这个任务队列还肩负着一个作用,就是核心线程如何能保持不被销毁的秘诀哦😯。

  • RejectedExecutionHandler:拒绝策略,默认的话是直接拒绝任务,还有其他的比如交给上层线程执行等,当然也可以自己重写。


2.3: 整体的过程

简答来说,一个任务来的时候->

  • 首先启动核心线程执行
  • 核心线程启动完还是执行不完,就会把任务放进等待队列
  • 等待队列也满了咋办?启动非核心线程执行任务
  • 非核心线程也满了咋办?执行对应的拒绝策略


2.4: 线程池初始化的时候会有几个线程?

刁钻问题来咯!

其实这个问题,如果了解守护线程和非守护线程的概念的话自己试一下就知道了。我们先new一个线程池,然后什么任务都不做。

    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(2, 100, 60L,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024));
        System.out.println("end");
    }

你会发现上面的jvm直接退出了,线程池构造的核心线程是非守护线程,而且会进行阻塞(后面会讲到)。那么聪明的你肯定猜到了,它肯定一个核心线程都没有构造,不然有非守护线程的存在,jvm是不会退出的。



然后让我们给他一个任务执行

    public static void main(String[] args) {
        ExecutorService pool = new ThreadPoolExecutor(2, 100, 60L,
                TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(1024));
        pool.submit(()->{
            System.out.println("do task");
        });
        System.out.println("end");
    }

这个时候执行完jvm就不会退出,说明已经有非守护线程运行在jvm中了。



2.5: 核心线程是如何增加的?

又是一个刁钻问题,假如说我的核心线程数是5个,当有1个核心线程在执行任务的时候,那么理所当然再来新的任务是会新启动新的线程,那如果第一个线程已经执行完了任务,那么再来新的任务,是启动新的线程还是让第一个线程去执行呢?


先说结论:答案是启动新的线程🤣🤣🤣,具体的要大家自己看源码了

2.6: 为什么核心线程可以保持不被销毁?

按道理说,我们的线程执行完任务后,正常就会结束生命周期被销毁,但是我们的线程池不可能这么蠢每次销毁再重新new,一定是复用了这些线程,让我们直接来看源码把。


  • 我们一路跟踪方法 execute -> addWorker -> 在这里会启动一个Worker类 -> 我们跟进去找到Worker类的runWorker方法 -> 执行getTask方法去执行任务

在最后跟到的代码我们可以看到,这是一个while的循环,红色框就是非核心线程的逻辑,非核心线程完成后就会return,自然结束了线程生命周期。 蓝色框就是核心线程的逻辑,任务没有用了就会阻塞在 workQuene.take(),从而保持核心线程能够不被销毁。🤣🤣🤣




3:如何构造合适的线程池

这个就要看你的具体的业务类型了:

  • 比如说你的任务是某个瞬间任务量很大,那么就需要调高最大线程数以及队列
  • 如果说你的任务是持续不断的产生任务平均散列在各个时间上的,那么可以增大核心线程数减少最大线程数和队列,保证第一时间的处理而不是让任务排队。