高频面试Java之线程池

简介

本专栏结合笔者面试的一些高频率题目,详细分析其原理、场景、底层机制、以及实际场景等。为了节约篇幅,我们注重分析一些原理、较为底层的东西,对于一些概念性的知识,不多赘述。也希望通过这个专栏,让大家在面试的过程中能够灵活应对。早日收到满意的offer

关于线程池的一些概念,如线程池是什么、为什么要使用线程池,不多赘述。

1、为什么不推荐使用Executors

线程池的创建方式有很多种,这也是面试最常问的问题,归根结底,面试官还是为了考察线程池的参数含义。不推荐使用 Executors的原因是默认参数不友好:

  1. FixedThreadPool和SingleThreadPool,队列长度为Integer.MAX_VALUE
  2. CachedThreadPool和ScheduledThreadPool:线程数量为Integer.MAX_VALUE
2、线程池核心参数分析
  1. corePoolSize:核心线程数
  2. maximumPoolSize:最大线程数
  3. workQueue:任务队列
  4. RejectedExecutionHandler 拒绝策略
  5. keepAliveTime:空闲时间
  6. timeUnit:时间单位
  7. threadFactory:线程工厂

针对参数会问到的是任务队列和拒绝策略,考察对java集合、拒绝策略的处理。

队列名称简单分析
ArrayBlockingQueue数组,ReentrantLock+Condition,生产者、消费者共用一把锁
LinkedBlockingQueue链表,ReentrantLock+Condition,生产者、消费者的锁各自独立,吞吐量高于ArrayBlockingQueue
SynchronousQueue不存储元素,插入操作必须等到另一个线程调用移除操作,吞吐量高于LinkedBlockingQueue
PriorityBlockingQueue带优先级

JDK自带策略有四种,但是对任务的处理都不友好,我们需要自定义拒绝策略,实现RejectedExecutionHandler即可

策略名对任务的处理方式
Abort抛出异常
CallerRuns使用调用者所在线程来运行任务
DiscardOldest丢弃队列里最近的一个任务,并执行当前任务
Discard直接丢弃掉
3、线程池生命周期

生命周期一般考察的不多,但是需要了解一下。

生命周期对应操作
running接收新任务,处理队列中的任务
shutdown不接收新任务,但是会处理队列中的任务
stop不接收新任务,不处理队列中的任务,并且中断正在执行的任务
tidying清空worker对象
terminated完全停止
4、线程池膨胀过程

该问题是最常考察的。主要针对 corePoolSize、maximumPoolSize、workQueue三个参数

在这里插入图片描述

5、线程池执行过程

线程池执行任务的流程如下,可结合源码分析:

  • 1、根据任务提交和执行情况,逐步饱和线程池。若线程数小于corePoolSize,创建线程;若任务队列未满,则将任务加入到队列中;若线程数小于maximumPoolSize,则创建线程;若线程池饱和,则根据回绝策略回绝新提交的线程
  • 2、调用addWorker方法,将任务包装成Worker对象。Worker类继承了线程Thread类,线程启动后,通过runWorker方法执行任务
  • 3、调用runWorker方法,先判断addWorker方法是否传递任务,若有,则执行任务;若无,则通过getTask()方法从队列中获取任务
  • 4、getTask方法,会判断当前线程数是否大于corePoolSize或者allowCoreThreadTimeOut是否为true,以决定是是以阻塞的形式还是以带超时时间的形式去获取任务,并决定是否回收线程
  • 5、如果线程应该被回收,则调用processWorkerExit方法回收线程,回收的时候要注意判断若线程池的状态为running或shutdown,并且任务队列不为空,则至少需要保留一个线程。但是由于回收线程在前,则需要再次通过addWorker方法创建一个线程,以用来执行队列中的任务
6、线程池大小预估

这个是比较常见的问题。包含线程数、队列大小两个方面

6.1 公式一

CPU 密集型应用,线程池大小设置为 N + 1;IO 密集型应用,线程池大小设置为 2N。这种估算方式网上比较常见,但是实际应用中很难界定到底什么是CPU 密集型应用、什么是IO 密集型应用。

6.2 公式二

core = tps(每秒能完成的事务数 = 并发量 / 平均响应时间)* time,max = tps * time * (1.7 ~ 2),该估算方式考虑到了业务场景,但是业务流量往往是不均匀分布的

6.3 公式三

线程数 = (1 + 线程等待时间/线程CPU时间 * 目标CPU的使用率 * 处理器核心数。例如:CPU Time 0.5ms,Wait Time 1.5ms,CPU使用率是90%,CPU核心数为8,(1 + 1.5/0.5) * 90% * 8 = 28.8。可以结合ThreadMXBean,计算出线程CPU时间、线程等待时间。

6.4 公式四

队列大小:queueSize = (coreSize/taskCost)*resTime。假设每个任务执行耗时,100ms,系统能够忍受的最大响应时间为2秒,线程池大小为32,那么队列大小可以设定为640

7、自定义线程池

实际应用中,可以自定义线程池,动态控制线程池的各个参数值、监控线程池的状态

自定义选项分析
线程数通过setCorePoolSize、setMaximumPoolSize动态设定线程数
队列自定义队列以支持长度的动态调整,拷贝LinkedBlockingQueue代码,提供setQueueSize方法
监控通过beforeExecute、afterExecute方法,监控线程最长执行时间、平均执行时长、线程活跃度、队列长度等,并提供预警
拒绝策略实现RejectedExecutionHandler接口,通过日志、持久化等方式存储不能被正常处理的任务以便将来恢复线程
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

闲来也无事

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值