前言:多线程知识是Java面试中必考的点。本文详细介绍——线程池。在实际开发过程里,很多IT从业者使用率不高,也只是了解个理论知识,和背诵各种八股文,没有深入理解到脑海里,导致面试完就忘。——码农 = 敲代码;程序员= 理解

线程池面试必考点:3大方法,7大参数,4种拒绝策略!

目录

▶ 介绍

一 . 线程池(Thread Pool)

二 . Executor、Executors 、ExecutorService 别再傻傻分不清!

▶ 逐一击穿

一 . 3大方法

二 . 7大参数

三 . 4种拒绝策略

▶ 最后


▶ 介绍

一 . 线程池(Thread Pool)

程序运行的本质就是:占用系统资源! 资源的竞争就会影响程序的运行,势必要优化资源的利用。例如:池化技术的诞生!常见的有:Java中的对象池、内存池、jdbc连接池、线程池等等

池化技术的原理:事先准备好资源,有人要用,就来我这里拿,用完再还给我!

我们知道创建、销毁线程十分浪费资源,不仅产生额外开销,还降低了计算机性能。使用线程池来管理线程,大白话总结就是:线程可复用,可控制最大并发数,线程方便管理

  • 减少线程频繁创建和销毁的开销!
  • 避免线程数过大导致过分调度cpu问题!
  • 提高了响应速度!

是为了最大化收益并最小化风险,而将资源统一在一起管理

二 . Executor、Executors 、ExecutorService 别再傻傻分不清!

Executors 工厂工具类   

是一个工具类, 提供工厂方法来创建不同类型的线程池供大家使用!

要什么样的线程池就new什么线程池给你,相当于一个工厂!!

该工厂提供的常见的线程池类型:

// 可缓存的线程池:工作线程如空闲了60秒将会被回收。终止后如有新任务加入,则重新创建一条线程
Executors.newCachedThreadPool(); // 固定线程数池:工作线程<核心线程数,则新建线程执行任务;工作线程=核心线程数,新任务则放入阻塞队列
Executors.newFixedThreadPool(); // 单线程池:只有一条线程执行任务,按照指定顺序(FIFO,LIFO)执行,新加入的任务放入阻塞队列(串行)
Executors.newSingleThreadExecutor(); // 定时线程池:支持定时及周期性任务执行
Executors.newScheduledThreadPool(); 

 Executor 执行者接口   

线程池的顶级接口,其他类都直接或间接实现它!只提供一个execute()方法,用来执行提交的Runnable任务——只是一个执行线程的工具类!!

public interface Executor {void execute(Runnable command);
}

ExecutorService 线程池接口 

线程池接口,继承Executor且扩展了Executor【执行者接口】,能够关闭线程池,提交线程获取执行结果,控制线程的执行。可以理解为:执行者接口Executor的升级版本!

public interface ExecutorService extends Executor {//温柔的终止线程池,不再接受新的任务,对已提交到线程池中任务依旧会处理void shutdown();//强硬的终止线程池,不再接受新的任务,同时对已提交未处理的任务放弃,并返回List<Runnable> shutdownNow();//判断当前线程池状态是非运行状态,已执行shutdown()或shutdownNow()boolean isShutdown();//线程池是否已经终止boolean isTerminated();//阻塞当前线程,等待线程池终止,支持超时boolean awaitTermination(long timeout, TimeUnit unit)throws InterruptedException;...
}

扩展:下表列出了 Executor 和 ExecutorService 的区别

Executor ExecutorService
Java 线程池的核心接口,用来并发执行提交的任务 是 Executor 接口的扩展子接口,提供了异步执行和关闭线程池的方法
提供execute()方法用来提交任务 提供submit()方法用来提交任务
无返回值 submit()方法返回Future对象,可用来获取任务执行结果
不能取消任务 可通过Future.cancel()取消pending中的任务
不支持关闭线程池 提供了关闭线程池的方法

▶ 逐一击穿

一 . 3大方法

指的是Executors工厂类提供的3种线程池。

上述讲解Executors工厂类说明了4种创建方式,这里只展示3种考的最多的!

a. 单线程池 

public static void main(String[] args) {// 1.定义一个单线程池:池中只有1条线程ExecutorService pool = Executors.newSingleThreadExecutor(); // 2.定义5条线程for (int i = 0; i < 5; i++) {pool.execute(()->{System.out.println(Thread.currentThread().getName()+" hi");});}// 3.关闭线程池pool.shutdown();
}
执行结果:从头到尾只有1条线程,且执行了5个任务

b. 可缓存的线程池 

public static void main(String[] args) {/** 说明:池中最大线程的创建数量为:Integer.MAX_VALUE(约等于21亿)如果线程超过60秒没执行任务,会自动回收该线程,译为可缓存的池子 */// 1.定义一个可缓存的线程池ExecutorService pool =Executors.newCachedThreadPool(); // 2.定义5条线程for (int i = 0; i < 5; i++) {pool.execute(()->{System.out.println(Thread.currentThread().getName()+" hi");});}// 3.关闭线程池pool.shutdown();
}

执行结果:池中被创建了5条线程执行5个任务,线程最大能创建多少条,取决于你的电脑性能

c. 固定线程池 

public static void main(String[] args) {// 1.定义一个固定长度为3的线程池ExecutorService pool =Executors.newFixedThreadPool(3); // 2.定义5条线程for (int i = 0; i < 5; i++) {pool.execute(()->{System.out.println(Thread.currentThread().getName()+" hi");});}// 3.关闭线程池pool.shutdown();
}

执行结果:池中定义了3个线程,所以执行任务的线程最多只有3条

考点分析:线程池为什么不允许使用 Executors 工厂类 去创建!!

答:规避资源耗尽的风险。弊端如下:(不能理解的话,见下面7大参数的讲解)

  • FixedThreadPool 和 SingleThreadPool:阻塞队列的任务容量为 Integer.MAX_VALUE (约21亿),会堆积大量的请求导致 OOM,造成系统瘫痪。

  • CachedThreadPool 和 ScheduledThreadPool:最大创建线程数量为 Integer.MAX_VALUE,创建大量的线程易导致 OOM。

快速记忆:固定或单一长度的线程池,队列容量没限制!非固定的池,创建线程数没限制!

二 . 7大参数

指的是自定义线程池中的7个设置参数(重点)

要点扩展:首先来查看下Executors工厂类提供的 3大方法的源码分析

// 固定长度线程池:nThreads为传入参数,最大线程数和核心线程数都为此值,固定线程数长度
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}// 单线程池:之所以只有1条线程执行,是因为核心线程数和最大线程数都是1
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));// 可缓存的线程池:最大创建数没有限制,设置了60秒空闲时间,空闲的线程超过此时间将会被回收
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

我们可以看到上述3个方法中,实际底层创建线程池都用到同一个 new ThreadPoolExecutor(),ThreadPoolExecutor是JUC提供的一类线程池工具,从字面含义来看,是指管理一组同构工作线程的资源池。通常理解为是一个自定义线程池。

来看下该类的构造参数说明:

/** corePoolSize:核心线程数maximumPoolSize:可创建的最大线程数keepAliveTime:存活时间,超过此time没任务执行的线程会被回收unit:存活时间的单位BlockingQueue:阻塞队列ThreadFactory:线程工厂,创建线程的,一般不用RejectedExecutionHandler:拒绝策略
*/
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory, RejectedExecutionHandler handler) {...
}

corePoolSize 核心线程数量

  • 默认情况只有当新任务到达时,才会创建和启动核心线程,可用 prestartCoreThread()启动一个核心线程 和 prestartAllCoreThreads() 启动所有核心线程的方法动态调整
  • 即使没任务执行,核心线程也会一直存活
  • 池内的线程数小于核心线程时,即使有空闲线程,线程池也会创建新的核心线程执行任务
  • 设置allowCoreThreadTimeout=true时,核心线程会超时关闭

maximumPoolSize 最大线程数

  • 当所有核心线程都在执行任务,且任务队列已满时,线程池会创建新非核心线程来执行任务
  • 当池内线程数=最大线程数,且队列已满,再有新任务会触发RejectedExecutionHandler策略

keepAliveTime TimeUnit 线程空闲时间

  • 如果线程数>核心线程数,线程的空闲时间达到keepAliveTime时,线程会被回收销毁,直到线程数量=核心线程
  • 如果设置allowCoreThreadTimeout=true时,核心线程执行完任务也会销毁直到数量=0

workQueue 任务队列

  • 队列的说明请参考 :

ThreadFactory 创建线程的工厂

  • 一般用来自定义线程名称,线程多的时候,可以分组区分识别

RejectedExecutionHandler 拒绝策略

  • 最大线程数和队列都满的情况下,对新任务的处理方式,请参考下方的4种策略

上述参数的说明太过于理论化,下面我将用生活的例子来说明重点参数的使用

模拟银行办理业务流程

  1. 银行就相当于一个线程池
  2. 银行内部最多有5个窗口可以办理业务( maximumPoolSize 最大线程创建数),只有前2个窗口正在办理业务中(corePoolSize 核心线程数),另外3个窗口处于暂停办理
  3. 等候区只有3个位置提供排队(workQueue队列容量为:3),并且已经排满了人

由图可得出的线程池参数为:

  • corePoolSize 核心线程数:2
  • maximumPoolSize 最大线程数:5
  • workQueue 任务队列大小:3

结论:如果核心窗口满了,则新来办理业务的人会进入排号等候区等待(阻塞队列)

思考:核心窗口和等候区都满了,那如果此时银行进来2个新办理业务的人呢?

由图可知,新进来2个人流程如下:

  1. 如果核心窗口被占用中(核心线程)则判断等候区 (阻塞队列)是否满了
  2. 排号区没满,判断等候区(阻塞队列)是否还有位置,如果有则进入等候区排队等待。
  3. 排号区和核心窗口都满了 (核心线程数+阻塞队列容量),那么就去判断银行的全部窗口(最大线程创建数)是否都在办理业务,如果没有,就开放2个新的窗口给新进来的2人办理业务(非核心窗口)

结论:核心线程数2 + 阻塞队列3 全部已满,如果工作线程还没达到最大线程数,线程池会给新进来的2个任务,开放创建2个新的非核心窗口来处理新任务

思考:那如果此时银行再来2个办理业务的人呢?

由图可知:

  1. 窗口5还没满,所以会被开放给新进来的其中一个人办理业务
  2. 而另外一个人,因5个窗口已全被占用,且等候区也满了,会被拒绝办理(拒绝策略)

总结,线程池的运行原理如下:

  1. 核心线程没满时,即便池中线程都处于空闲,也创建新核心线程来处理新任务。

  2. 核心线程数已满,但阻塞队列 workQueue未满,则新进来的任务会放入队列中。

  3. 核心线程数和阻塞队列都满了,如果当前线程数< 最大线程创建数, 会创建新 (非核心)线程来处理新任务

  4. 如三者都满了(核心线程数、阻塞队列、最大线程数)则通过指定的拒绝策略处理任务

优先级为:核心线程corePoolSize、任务队列workQueue、(非核心线程) 最大线程maximumPoolSize,如三者都满了,采用handler拒绝策略

三 . 4种拒绝策略

指的是线程池中的最大线程数和队列都满的情况下,对新进来任务的处理方式

  • CallerRunsPolicy(调用者运行策略):使用当前调用的线程 (提交任务的线程) 来执行此任务

  • AbortPolicy(中止策略):拒绝并抛出异常 (默认)

  • DiscardPolicy(丢弃策略):丢弃此任务,不会抛异常

  • DiscardOldestPolicy(弃老策略):抛弃队列头部(最旧)的一个任务,并执行当前任务

下面我将用代码来进行演示说明:

CallerRunsPolicy(调用者运行策略)

/**corePoolSize核心线程数:2maximumPoolSize最大线程数:3workQueue阻塞队列容量:2RejectedHandler拒绝策略:CallerRunsPolicy 调用者运行
*/
public static void main(String[] args) {// 1.自定义一个线程池ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 3, 0, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(2), new ThreadPoolExecutor.CallerRunsPolicy()); // 指定拒绝策略// 2.提交6个任务for (int i = 0; i < 6; i++) {final int num =i;pool.execute(()->{System.out.println(Thread.currentThread().getName()+" ok:"+num);});}// 3.关闭线程池pool.shutdown();}

执行结果如下:

说明:首先提交了6个任务,而线程池可接收的线程容量为:队列2 + 最大创建线程数3 = 5个,因为最大线程创建数为3,所以最多只有3个线程去轮询执行5个任务,多余的第6个任务,线程池因为占满了故没办法运行,线程池指定的拒绝策略是:调用者运行策略  也就是提交任务的线程去处理,即main线程

AbortPolicy(中止策略)

与上述调用者策略的代码一致,修改ThreadPoolExecutor后面的具体策略类型即可

new ThreadPoolExecutor.AbortPolicy(); // 指定拒绝策略

执行结果如下:

说明:由于采用的是中止策略,拒绝任务并抛出异常,第6个任务因线程池已满,而无法执行。

DiscardPolicy(丢弃策略)

new ThreadPoolExecutor.DiscardPolicy(); // 指定拒绝策略

执行结果如下:

说明:第6个任务被丢弃了,结束程序运行

DiscardOldestPolicy(弃老策略)

new ThreadPoolExecutor.DiscardOldestPolicy(); // 指定拒绝策略

执行结果如下:

说明:首先核心线程是2个,故任务1、2直接进入线程池被核心线程执行,而任务3进来后,核心线程已满,则进入队列等待,任务4随后也进入队列,任务5进来后,因为核心线程和队列都已满,但还没有达到最大线程创建数3,故会创建一条非核心线程去处理任务5。此时池中已达到最大线程数,而队列也占满了,最后任务6进来,所以会抛弃最早进入队列的任务3

▶ 最后

  1. 本文没有很深入的讲解很多源码知识,只是带着读者理解:线程池是什么?怎么用?
  2. 面试的必考点知识,基本上都是围绕着:3大方法、7大参数、4种拒绝策略 来问问题,如果你能够读懂本篇文章,基本上已经掌握了线程池的知识点!
  3. 面试造火箭,上班拧螺丝不可耻!死记硬背八股文才可耻,只有真正理解在自己脑海里的知识才是有所收获,否则你看的每一篇文章都只是别人的笔记。面试前背,面试完就忘,坚决拒绝短暂的记忆,带着自己的方式去理解它,这才是我想要表达的观点!

优秀的判断力来自经验,但经验来自于错误的判断。

击穿线程池面试题:3大方法,7大参数,4种拒绝策略相关推荐

  1. java线程池面试题有哪些?java线程池常见面试题

    进行java面试的过程中,java线程池是必问的面试题目,因为这是java的重点知识,也是在java工作中经常会遇到的,那java线程池面试题有哪些?下面来我们就来给大家讲解一下java线程池常见面试 ...

  2. 线程池面试题一般会怎么问?线程池面试题总结及答案整理

    对于广大程序员来说,线程池一定不会陌生,因为大部分程序员面试时总会被问到关于线程池的问题,今天总结了一些关于线程池的各种面试可能问到的题目,希望对大家有所帮助. 一.线程池是什么? 答:线程池,是一种 ...

  3. 线程池三大方法,七大参数,四种拒绝策略

    线程和进程: 进程: 一个程序,是执行程序的一次执行过程. 一个进程往往包含若干个线程,线程是cpu调度和执行的单位. Java默认有2个线程:main.GC 池化技术: 01:程序的运行,本质 :占 ...

  4. 线程池:4个方法,7个参数,4种拒绝策略

    什么是池? 先讲一个例子,有可能可以帮助你理解,觉得无趣的小伙伴可以直接跳过.相信大多数都知道外包公司,甚至很多小伙伴还在外包公司呆过,其实外包公司我觉得也就可以看作是个"池". ...

  5. 线程池的四种拒绝策略

    一.前言 线程池,相信很多人都有用过,没用过相信的也有学习过.但是,线程池的拒绝策略,相信知道的人会少许多. 二.四种线程池拒绝策略 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximum ...

  6. Java多线程学习七:线程池的 4 种拒绝策略和 6 种常见的线程池

    以便在必要的时候按照我们的策略来拒绝任务,那么拒绝任务的时机是什么呢?线程池会在以下两种情况下会拒绝新提交的任务. 第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部 ...

  7. 金九银十:线程、多线程,线程池面试题十连问!

    点击上方关注 "终端研发部" 设为"星标",和你一起掌握更多数据库知识 大家好我是小于哥啊,最近的面试中,关于线程.多线程,线程池相关的面试题还是挺多的.今天我 ...

  8. 线程池面试题灵魂三问

    线程池面试问题 1.日常工作中有用到线程池吗?什么是线程池?为什么要使用线程池? 2.ThreadPoolExecutor都有哪些核心线程? 3.什么是阻塞队列?说说常用的阻塞队列? 1.日常工作中有 ...

  9. java线程池的面试题_献给准备面试的你,Java线程and线程池面试题小结

    最近这几天一直在整理Java相关的面试题,"金九银十"是求职的最佳时间,但是现在的"银十"也已经过去了一半的时间,相信现在还在为面试四处奔波的小伙伴已经很疲惫了 ...

最新文章

  1. LLVM与Clang局部架构与语法分析
  2. catia今天突然打不开了_苹果手机锁屏密码突然不正确了?不要慌!也先不要着急刷机!!!尝试一下以下方式!...
  3. POJ 3278 Catch That Cow
  4. docker 登陆mysql_启用登录docker mysql容器
  5. VTK修炼之道71:交互与Widget_观察者/命令模式
  6. abb变频器如何就地增加频率_abb变频器报接地故障如何处理,故障原因分析
  7. nv4_disp.dll 蓝屏
  8. 详解:设计模式之-策略设计模式
  9. 算法导论 CLRS 22.4-4 解答
  10. linux+读取初始化文件,Linux 初始化系统 SystemV Upstart
  11. Freemarker 最简单的例子程序
  12. python 将字符串转换为字典
  13. 对初学者来说,Python难度不低于其他语言
  14. Java 拓扑图构建_用JAVA画个简单的拓扑图
  15. 抖音服务器维护中怎么改名字,抖音怎么更改名字
  16. 好心情患者故事:节食暴食反复横跳,我确诊了重度抑郁
  17. win7为什么安装不了python_【如何在win7下安装Python及配置】电脑无法安装python
  18. eve手游服务器维护,EVE手游国际服新手教程,云手机小号多开辅助快速获得资源...
  19. 融会贯通,从oracle...,融会贯通Oracle数据库的25条基本知识:
  20. js本地刷新和局部刷新

热门文章

  1. 吉利有后手,魅族没有
  2. 4大私域流量体系(个人号、公众号、社群和小程序)全方面价值对比:私域流量,企业保命之本爆发之源!...
  3. 电脑重置后需要清除tpm吗
  4. AddMvcCore,AddControllers,AddControllersWithViews,AddRazorPages的区别
  5. Python的len函数探究
  6. 主谓一致 | 谓语动词单复数使用讲解
  7. VC(Visual Studio C++)虚拟键VK值列表
  8. abaqus各种文件说明
  9. Spring MVC详解(学习总结)
  10. (十二)苏世民:我的经验和教训:苏世民带领黑石走向巅峰的十大管理原则