Java岗大厂面试百日冲刺【Day47】— 并发编程4
Java岗大厂面试百日冲刺【Day47】— 并发编程4
本文已获得原作者 _陈哈哈 授权并经过重新整理规划后发布。
本栏目Java开发岗高频面试题主要出自以下各技术栈:Java基础知识、集合容器、并发编程、JVM、Spring全家桶、MyBatis等ORMapping框架、MySQL数据库、Redis缓存、RabbitMQ消息队列、Linux操作技巧等。
- 在《阿里Java开发代码规范》中表示,线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。即
在使用多线程时,均要通过线程池创建
。
面试题1:说一下线程池都是怎么创建的?
线程池可以自动创建
也可以手动创建
。
自动创建线程池
自动创建体现在Executors工具类中,常见的可以创建newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool;
public static ExecutorService newFixedThreadPool(int var0) {
return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public static ScheduledExecutorService newScheduledThreadPool(int var0) {
return new ScheduledThreadPoolExecutor(var0);
}
手动创建线程池(推荐)
手动创建体现在可以灵活设置线程池的各个参数,体现在代码中即ThreadPoolExecutor类构造器上各个实参的不同:
/**
* @Description 配置线程池实例,用于调用
*/
@Configuration
public class ThreadPoolConfig {
@Bean(value = "threadPoolInstance")
public ExecutorService createThreadPoolInstance() {
//通过guava类库的ThreadFactoryBuilder来实现线程工厂类并设置线程名称
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build();
ExecutorService threadPool = new ThreadPoolExecutor(10, 16, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());
return threadPool;
}
}
//通过name=threadPoolInstance引用线程池实例
@Resource(name = "threadPoolInstance")
private ExecutorService executorService;
@Override
public void spikeConsumer() {
// 线程池启动线程
executorService.execute(new Runnable() {
@Override
public void run() {
// 可执行代码块儿
}});
}
追问1:线程池都有哪些参数?
// 默认拒绝策略为 AbortPolicy
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {}
corePoolSize
:核心线程数,也是线程池中常驻的线程数,线程池初始化时默认是没有线程的,当任务来临时才开始创建线程去执行任务maximumPoolSize
:最大线程数,在核心线程数的基础上可能会额外增加一些非核心线程,需要注意的是只有当workQueue队列填满时才会创建多于corePoolSize的线程(线程池总线程数不超过maxPoolSize)keepAliveTime
:非核心线程的空闲时间超过keepAliveTime就会被自动终止回收掉,注意当corePoolSize=maxPoolSize时,keepAliveTime参数也就不起作用了(因为不存在非核心线程);unit
:keepAliveTime的时间单位workQueue
:用于保存任务的队列,可以为无界、有界、同步移交三种队列类型之一,当池子里的工作线程数大于corePoolSize时,这时新进来的任务会被放到队列中threadFactory
:创建线程的工厂类,默认使用Executors.defaultThreadFactory(),也可以使用guava库的ThreadFactoryBuilder来创建handler
:线程池无法继续接收任务(队列已满且线程数达到maximunPoolSize)时的饱和策略,取值有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy
// 默认拒绝策略为 AbortPolicy
ExecutorService threadPool = new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100), threadFactory, new ThreadPoolExecutor.AbortPolicy());
追问2:线程池都有哪几种工作队列(WorkQueue)?分别有什么特点?
常用
- ArrayBlockintQueue(有界队列):基于数组的有限阻塞队列,先进先出(FIFO),插入数据和取出数据
公用一个锁对象
; - LinkedBlockingQueue(无界队列):基于链表的无限阻塞队列,先进先出(FIFO),当请求越来越多时(任务处理速度跟不上任务提交速度造成请求堆积)
可能导致内存占用过多或OOM
,newFixedThreadPool 和 newSingleThreadExecutor 使用这个队列; - SynchronousQueue(同步移交队列):不存储元素的阻塞队列,插入操作必须等待一个线程调用移除操作,否则一直会阻塞。newCachedTHreadPool 使用这个队列
不常用
- PriorityBlockingQueue:有优先级的无限阻塞队列,基于最小二叉堆实现
- DelayedWorkQueue:每个元素指定延时时间,只有当时间到了才能从队列中获取该元素,是一个无限队列。
面试题2:线程池提交一个任务经过哪些步骤?
线程池提交任务流程为:
- 当线程池新加入一个线程时,首先判断当前线程数,是否小于corePoolSize(核心线程数),如果小于,则执行步骤2,否则执行3;
- 创建新线程添加到线程池中,流程结束;
- 判断当前线程池等待队列是否已满,若已满,则跳转至步骤5;
- 加入等待队列,等待线程池空闲,流程结束;
- 判断当前线程数是否已达到maximumPoolSize(最大线程数),若未达到,则跳转至步骤7;
- 执行线程池拒绝策略,流程结束;
- 创建一个新线程,执行任务;
- 流程结束;
比如现有一个线程池,corePoolSize=10,maxPoolSize=20,队列长度为100,那么当任务过来会先创建10个核心线程数
,接下来进来的任务会进入到队列中直到队列满了,会创建额外的线程
来执行任务(最多20个线程),这个时候如果再来任务就会执行拒绝策略
。
追问1:线程池的拒绝策略了解么?
AbortPolicy(默认)
:中断直接抛异常DiscardPolicy
:默默丢弃任务,不进行任何通知DiscardOldestPolicy
:丢弃掉在队列中存在时间最久的任务CallerRunsPolicy
:让提交任务的线程去执行任务
面试题3:关闭线程池的方式都有哪些?
shutdownNow()
:立即关闭线程池(强制),正在执行中的及队列中的任务会被中断,同时该方法会返回被中断的队列中的任务列表;shutdown()
:平滑关闭线程池,正在执行中的及队列中的任务能执行完成,后续进来的任务会被执行拒绝策略;isTerminated()
:当正在执行的任务及对列中的任务全部都执行完就会返回,return true;