面试-线程池原理

前言

线程池有哪些

使用过的话,这个应该可以答出来,固定大小的线程池 newFixedThreadPool,单线程线程池 newSingleThreadExecutor,无限大小的线程池 newCachedThreadPool ,和周期性执行线程池 newScheduledThreadPool

它们都是构造了一个 ThreadPoolExecutor 实例,使用了不同的参数而已,可能会问你使用场景,这时你可以这样甩它一脸,一般都不会用 JDK 定义好的,因为有坑,而是自己使用参数构建一个 ThreadPoolExecutor 实例。

线程池的执行流程

基本面试必问,可能会以另一种方式问你有几个核心参数或者让你设计一个线程池你会如何设计。

线程池有这样一些参数 ,基本上池相关的,都会有这些,像 tomcat 的连接池,数据库连接池,redis 连接池,http 连接池。

  • 核心线程数 corePoolSize,多出的线程会被销毁
  • 最大线程数 maximunPoolSize,当处在高峰期时所能创建的最大线程数
  • 非核心线程保留时间 keepAliveTime, 非核心线程能够空闲的最大时间,如超过时间,则销毁
  • 缓存队列 workQueue,当创建的线程数超过核心线程数时,线程会进入等待队列等待,直到队满

执行流程如下,在 ThreadPoolExecutor.execute 有详细英语说明

  1. 在当前池中线程数还少于核心线程数时,会先创建线程,把当前任务做为这个线程的第一个任务来执行
  2. 当池中线程数超过核心线程数后,会先存储任务到队列,并且检查当前池中线程数,因为可能有挂掉的线程需要补充
  3. 如果队列也加不进线程了,尝试添加新的线程,直到最大线程数,如果撑不住了,执行拒绝策略
  4. 过了高峰期后,如果有设置 keepAliveTime ,会把线程数量降到核心线程数

可能会问 execute 和 submit 有什么区别

submit 其实就是调用了 execute ,submit 可以有返回值 Future 用于异步返回

可以使用的缓存队列有哪些,有什么特点

LinkedBlockingQueue 基于链表实现的队列,如果不指定容量,默认就为 Integer.MAX_VALUE,它是 newFixedThreadPool 和 newSingleThreadExecutor 默认使用的队列,当在高峰期时,会把内存撑爆

ArrayBlockingQueue 基于数组实现的队列,和 LinkedBlockingQueue 类似

SynchronousQueue 没有容量,必须要等添加的数据被消费后才能继续添加元素,用在 newCachedThreadPool 线程池中,它的最大线程数为Integer.MAX_VALUE 所以这种线程池在高峰时会不断的创建线程,直到不能创建线程耗尽资源

DelayedWorkQueue 使用堆实现的优先级队列,会不断增长空间,最大线程数也是 Integer.MAX_VALUE ,但定时任务不会有太高的并发,不过定时任务一般不使用这个,而是一些 quartz 任务框架,可以将任务持久 ,如果是在多实例部署的情况下,还应该使用分布式任务 xxljob 或者 elasticjob

拒绝策略

拒绝策略用于当队列满了,并且线程数了已经到达最大线程数时,执行的策略。

Jdk 内置拒绝策略 有抛弃当前任务,抛弃旧任务,异常,运行在提交任务的线程,默认是异常,除了运行在提交任务线程外,其它的都会拒绝当前任务,运行在提交任务线程也会阻塞下一个任务的提交,所以这个拒绝策略经常也需要重写才能达到要求。

第三方的拒绝策略有 dubbo 中的也是异常,但会打印出更加详细的信息出来 , netty 会新起线程来运行任务,activeMq 会等一分钟后重新放入队列,pinpoint 使用责任链模式执行一个拒绝链。

以上拒绝策略详情见博客说明 http://www.kailing.pub/article/index/arcid/255.html

我之前也写过一种拒绝策略,使用斐波拉契数列做为等待时间,初始参数为 100ms,100ms,每次等待一段时间后重新提交任务, 在等待时间超过 3 分钟后还没有执行任务,抛出异常。

/**
     * 线程池拒绝策略,等待一会(<斐波拉契数列:初始值为 100,100>),直到线程池空闲,然后再次提交线程;
     * 当线程池空闲时间过长(1 min)后,初始化等待时间
     * 当线程池过载,等待时间过长 (3 min) 后,抛出异常
     */
class DefaultRejectedExecutionHandler implements RejectedExecutionHandler {
    private Log logger = LogFactory.getLog(DefaultRejectedExecutionHandler.class);

    // 上次等待时间
    private long lastWaitTime = 100;
    private long lastLastWaitTime = 100;
    private long maxWaitTime = DateUtils.MILLIS_PER_MINUTE * 3 ;
    private long maxIdleTime = DateUtils.MILLIS_PER_MINUTE;

    //最后一次池子不空闲时间
    private long lastPoolUnIdleTime = System.currentTimeMillis();

    @Override
    public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) {
        if(System.currentTimeMillis() - lastPoolUnIdleTime >= maxIdleTime){
            //如果池子空闲时间超过最大值,下次等待时就初始化为最开始等待时间
            lastWaitTime = 100;
            lastLastWaitTime = 100;
        }

        if(lastWaitTime >= maxWaitTime){
            //如果上次等待时间超过 3 分钟 ,则需要增大线程池大小,并需要查询什么操作耗时过长
            logger.error("上次等待时间["+lastWaitTime+"]超过最大等待时间["+maxWaitTime+"] ms,当前请求获取线程被拒绝;当前线程池配置 ==> \n" +
                         " 最大池大小["+maxPoolSize+"],核心维护大小["+corePoolSize+"],排队数["+queueCapacity+"]");
            return ;
        }

        //当前池子不是空闲的,记录池子最后不是空闲的时间
        lastPoolUnIdleTime = System.currentTimeMillis();

        //如果等待时间未超过最大等待时间,则以 <斐波拉契数列> 获取等待时间 200,300,500,800,1300...
        long nowWaitTime = lastWaitTime + lastLastWaitTime ;

        try {
            //否则等待一段时间后重新提交线程
            Thread.sleep(nowWaitTime);
            executor.submit(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        lastLastWaitTime = lastWaitTime;
        lastWaitTime = nowWaitTime;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章