面试阿里问到线程池的问题,“对不起,我学习过这篇线程池讲解,斩神行动开始”

本文详细介绍了面试中常见的线程池问题,包括线程池的概念、优点、结构、创建规范和常见类型的使用。通过分析ExecutorService接口、ThreadPoolExecutor类及其重要参数,阐述线程池如何降低性能开销、提高响应速度。同时,讨论了四种线程池(FixedThreadPool、ScheduledThreadPool、CachedThreadPool、SingleThreadExecutor)的应用场景和工作流程。


面试官神技:“如来神掌”

请你说一下线程池的原理和使用?

提示:以下是本篇功法,记得收藏关注

见招拆招:简述概念以及优点

优点:

  1. 降低创建线程和销毁线程的性能开销
  2. 提高响应速度,当有新任务需要执行是不需要等待线程创建就可以 立马执行。
  3. 合理的设置线程池大小可以避免因为线程数过多消耗CPU资源

见龙在田:究其结构与使用规范

1.uml类图结构:

在这里插入图片描述
①Executor:可以看到最顶层是 Executor 的接口。这个接口很简单,只有一个 execute 方法。此接口的目的是为了把任务提交和任务执行解耦。

②ExecutorService:这还是一个接口,继承自 Executor,它扩展了 Executor 接口,定义了更多线程池相关的操作。

③AbstractExecutorService:提供了 ExecutorService 的部分默认实现。

④ThreadPoolExecutor:实际上我们使用的线程池的实现是 ThreadPoolExecutor。它实现了线程池工作的完整机制。也是我们接下来分析的重点对象。

⑤ForkJoinPool:和ThreadPoolExecutor都继承自AbstractExecutorService,适合用于分而治之,递归计算的算法

⑥ScheduledExecutorService:这个接口扩展了ExecutorService,定义个延迟执行和周期性执行任务的方法。

⑦ScheduledThreadPoolExecutor:此接口则是在继承 ThreadPoolExecutor 的基础上实现 ScheduledExecutorService 接口,提供定时和周期执行任务的特性。

2.创建线程池的主要使用

在这里插入图片描述
通过阿里的开发手册我们看到因为允许的阻塞队列的长度为Integer.MAX_VALUE,所以会出现OOM的问题,我们还是使用ThreadPoolExecutor构造方法进行线程池的创建。

public ThreadPoolExecutor(int corePoolSize, //核心线程数量
                          int maximumPoolSize, //最大线程数
                          long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间
                          TimeUnit unit, //存活时间单位
                          BlockingQueue<Runnable> workQueue, //保存执行任务的队列
                          ThreadFactory threadFactory,//创建新线程使用的工厂
                          RejectedExecutionHandler handler //当任务无法执行的时候的处理方式)
                            

注意:上面对其重要参数进行了简要讲解,源码系列发在下一篇
下面是线程池的基本创建示例:

// 创建线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE,
                                             MAXIMUM_POOL_SIZE,
                                             KEEP_ALIVE,
                                             TimeUnit.SECONDS,
                                             sPoolWorkQueue,
                                             sThreadFactory);
// 向线程池提交任务
threadPool.execute(new Runnable() {
    @Override
    public void run() {
        ... // 线程执行的任务
    }
});
// 关闭线程池
threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,然后中断所有没有正在执行任务的线程
threadPool.shutdownNow(); // 设置线程池的状态为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表

corePoolSize:即线程池的核心线程数量,其实也是最小线程数量。不设置allowCoreThreadTimeOut 的情况下,核心线程数量范围内的线程一直存活。线程不会自行销毁,而是以挂起的状态返回到线程池,直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。

maximumPoolSize:即线程池的最大线程数量

keepAliveTime和unit:超出核心线程数后的存活时间和存活单位

workQueue:是一个阻塞的 queue,用来保存线程池要执行的所有任务。通常可以取下面三种类型:

1)ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小; 2)LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE; 3)SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

ThreadFactory:我们一般用Executors.defaultThreadFactory()默认工厂,为什么要用工厂呢,其实就是规范了生成的Thread。避免调用new Thread创建,导致创建出来的Thread可能存在差异

handler:当队列和最大线程池都满了之后的拒绝策略。

1.AbortPolicy:直接抛出异常,默认策略; 2.CallerRunsPolicy:用调用者所在的线程来执行任务; 3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; 4、DiscardPolicy:直接丢弃任务; 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录 日志或持久化存储不能处理的任务
以上是对重要参数的基本使用,在核心参数下,线程池的性能和抗压能力会在参数的合理性下匹配,

线程池的工作流程

在这里插入图片描述

致命一击:常用线程池的使用

1.定长线程池(FixedThreadPool)

// 1. 创建定长线程池对象 & 设置线程池线程数量固定为3
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
fixedThreadPool.execute(task);

特点:只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列。
应用场景:控制线程最大并发数。

2.定时线程池(ScheduledThreadPool )

// 1. 创建 定时线程池对象 & 设置线程池线程数量固定为5
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
scheduledThreadPool.schedule(task, 1, TimeUnit.SECONDS); // 延迟1s后执行任务
scheduledThreadPool.scheduleAtFixedRate(task,10,1000,TimeUnit.MILLISECONDS);// 延迟10ms后、每隔1000ms执行任务```

特点:核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。
应用场景:执行定时或周期性的任务。

3.可缓存线程池(CachedThreadPool)

// 1. 创建可缓存线程池对象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
cachedThreadPool.execute(task);

特点:无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。
应用场景:执行大量、耗时少的任务。

4. 单线程化线程(SingleThreadExecutor)

// 1. 创建单线程化线程池
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
// 2. 创建好Runnable类线程对象 & 需执行的任务
Runnable task =new Runnable(){
  public void run() {
     System.out.println("执行任务啦");
  }
};
// 3. 向线程池提交任务
singleThreadExecutor.execute(task);

特点:只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。
应用场景:不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

总结:斩神行动

1.对于创建线程池依据规范,也要注意核心参数的含义和取值,根据实际场景进行参数配置,
2.对于初学者来说完全够用了,源码会在后面进行发布讲解,我们都是路人,无非走个你前我后。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值