【Java学习笔记】线程池详解:从理解到应用
创始人
2025-06-01 19:09:53

想要了解的部分

  • 线程池创建的七大参数
  • 如何创建线程池
  • 拒绝策略如何选择
  • 什么场景需要使用什么线程池
  • 如何使用线程池
  • 为什么阿里规范不能使用Executors创建线程池?

参考资料+ 图灵学院的直播公开课

文章目录

    • 01 线程的七大参数
      • 线程池内各个线程执行的顺序
        • 1)提交优先级
        • 2)执行优先级
    • 02 如何创建线程池
      • 一、newCachedThreadPool (纯缓存线程池)
      • 二、newFixedThreadPool (固定大小的线程池)
      • 三、newSingleThreadExecutor(单一线程池)
        • :star: 衍生问题:线程池的队列Queue是如何选择的?
          • (1)SynchronousQueue
          • (2)LinkedBlockingQueue
          • (3)ArrayBlockingQueue
    • 03 拒绝策略如何选择?
      • 一、拒绝策略是什么时候触发?
      • 二、拒绝策略的类型
        • 1)AbortPolicy (默认的流产策略)
        • 2)CallerRunsPolicy (调用者执行策略)
        • 3)DiscardOldestPolicy (最老抛弃策略)
        • 4)DiscardPolicy (静默抛弃原则)
    • 04 如何使用线程池
      • 一、两个方法 excute和submit的区别
      • 二、衍生问题:如何处理callable线程的返回数据?
        • 简单处理:
        • 更高级的处理
        • 值得注意的是,经过测试,可以这么简单总结Callable的线程使用情况。
    • 05 线程池的使用场景
      • 一、取代原本多线程应用到的场景
      • 二、针对executors类生产的几种线程池的常用场景
        • 1、newCachedThreadPool
        • 2、newFixedThreadPool /
        • newSingleThreadExecutor 同2
        • 3、:star: newScheduledThreadPool (定时线程池)
      • 三、定时线程池详解 :ScheduledThreadPool (定时线程池)
      • 1、创建
      • 2、使用
        • 1)scheduleAtFixedRate (固定速度执行计划
        • 2)scheduleWithFixedDelay ( 固定延期安排
    • 06 阿里规范为什么禁止使用使用 Executors 去创建线程池
        • 1) FixedThreadPool 和 SingleThreadPool:
        • 2) CachedThreadPool:

01 线程的七大参数

翻看源码可以看到线程池对象的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,	// 核心线程数int maximumPoolSize,	// 最大线程数long keepAliveTime,	// 线程可存活时间TimeUnit unit,		// 时间单位BlockingQueue workQueue,	// 阻塞队列ThreadFactory threadFactory,	// 线程工厂RejectedExecutionHandler handler	// 拒绝策略) {

同时比较常用的可以看到5参数版的构造函数:

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
}
  • 线程公司使用了默认的,如下

    new DefaultThreadFactory()
    
  • 拒绝策略也指向了默认的,如下

    RejectedExecutionHandler defaultHandler = new AbortPolicy();
    

线程池内各个线程执行的顺序

七大参数之间的关系看这个视频。

关于执行的顺序,涉及到两个概念 【提交优先级】和【执行优先级】

1)提交优先级

简单理解,当一个新的线程被提交时,会优先提交给核心线程,然后是队列,队列不足时才会创建新的线程(直到最大线程数)

可以查看源码 ,出处

image-20230322114010471

看图

image-20230322113842706

2)执行优先级

但是在执行线程池,顺序就不一样了,优先执行核心线程,再执行临时线程,最后再执行队列中的线程

02 如何创建线程池

先了解一下Executors提供的几种常用的线程池配置:

image-20230322104441120

一、newCachedThreadPool (纯缓存线程池)

源码如下:

public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue());
}
  • 核心线程为0,而最大线程为无限(几乎) ——所以的线程都是临时搭建,超过一定的时间后,就会自动销毁
  • 使用的队列 是:SynchronousQueue (翻译过来叫同步队列

优点:

  • 拥有最快的速度,调用了最多的线程

缺点:

  • 创建多线程会占用CPU资源,会导致CPU占用过高!

二、newFixedThreadPool (固定大小的线程池)

public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue());
}
  • 核心线程数 = 最大线程数 = 创建时传入的int大小
    • 表示没有临时线程
  • 使用的是一个链表队列LinkedBlockingQueue

优点:

  • 速度适中,占用的CPU资源固定

缺点:

  • 大量的任务会导致内存溢出!因为超出的线程都会占用内存空间

三、newSingleThreadExecutor(单一线程池)

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue()));
}
  • 核心线程数固定为1 的 FixedThreadPool

优点:

  • CPU占用低

缺点:

  • 和fixed一样,内容可能会溢出

⭐️ 衍生问题:线程池的队列Queue是如何选择的?

(1)SynchronousQueue

(API)一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有.

简单理解:在用不到队列的使用,选择该队列

(2)LinkedBlockingQueue

表示无限长的队列,所以FixedThreadPool中即使没有临时线程,超出部分的线程就会全部存入内存中进行等待

简单理解:在大量使用队列时,选择该队列

使用该队列有一个极大的风险:存在内容溢出问题

(3)ArrayBlockingQueue

(API) 这是一个典型的**“有界缓存区”**,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素

可以声明长度大小的队列,声明时必须传入长度int

简单理解:比较普遍情况使用的队列,一般可以用于自定义声明线程池时使用。放置内容溢出。

03 拒绝策略如何选择?

一、拒绝策略是什么时候触发?

image-20230322113842706

从前面的提交优先级分析就可以看到,只有当提交任务出现【核心线程、队列、临时线程】都满了的情况时,就会触发拒绝策略

二、拒绝策略的类型

目前所有的拒绝策略都实现于RejectedExecutionHandler接口

image-20230322114422970

实现类有以下四种拒绝策略

1)AbortPolicy (默认的流产策略)

永远都是直接抛出异常(一旦进入拒绝就抛出异常),该任务将会直接被忽略跳过
image-20230322114010471

2)CallerRunsPolicy (调用者执行策略)

(api)用于被拒绝任务的处理程序,它直接在 execute 方法的调用线程中运行被拒绝的任务;如果执行程序已关闭,则会丢弃该任务。

简单理解:让线程池的调用者自己去执行这个线程的run()方法

aaa

测试一下:写一个简单的demo,不难发现当触发拒绝策略时,main线程执行了run方法

在这里插入图片描述

3)DiscardOldestPolicy (最老抛弃策略)

(api)用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试 execute;如果执行程序已关闭,则会丢弃该任务。

4)DiscardPolicy (静默抛弃原则)

(api)用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。

04 如何使用线程池

思路如下

  1. 创建线程池(前面已经提到
  2. 提交/执行任务(涉及到两个方法 excute和submit
  3. 销毁线程池 ( 涉及shutdown

一、两个方法 excute和submit的区别

img

img

两个方法都可以将任务提交给线程池进行管理并执行,区别在于:

  • execute只能提交Runnable类型的任务,无返回值。submit既可以提交Runnable类型的任务,也可以提交Callable类型的任务,会有一个类型为Future的返回值,但当任务类型为Runnable时,返回值为null。
  • execute在执行任务时,如果遇到异常会直接抛出,而submit不会直接抛出,只有在使用Future的get方法获取返回值时,才会抛出异常。

二、衍生问题:如何处理callable线程的返回数据?

简单处理:

Callable的返回类型是Future,调用future.get()就可以实现异常的抛出了。实例如下

public class TestThreadPoolBegin {public static void main(String[] args) throws Exception{ExecutorService es = Executors.newSingleThreadExecutor();Callable callable = new Callable() {@Overridepublic Object call() throws Exception {System.out.println("线程处理开始...");int a = 0;int b = 3;System.out.println("除以0的结果为:" + b/a);System.out.println("线程处理结束...");return "0";}};Future future = es.submit(callable);System.out.println("任务执行完成,结果为:" + future.get());es.shutdown();}
}

更高级的处理

future的get方法在未获得返回值之前会一直阻塞,我们可以使用future的isDone方法判断任务是否执行完成,然后再决定是否get,因此上述代码我们可以优化如下:

public class TestThreadPoolBegin {public static void main(String[] args) throws Exception{ExecutorService es = Executors.newSingleThreadExecutor();Callable callable = new Callable() {@Overridepublic String call() throws Exception {System.out.println("线程处理开始...");int a = 2;int b = 3;System.out.println("3/2的结果为:" + b/a);System.out.println("线程处理结束...");return "0";}};Future future = es.submit(callable);while(true) {//idDone:如果任务已完成,则返回 true。 可能由于正常终止、异常或取消而完成,在所有这些情况中,此方法都将返回 true。if(future.isDone()) {System.out.println("任务执行完成:" + future.get());break;}}es.shutdown();}
}

值得注意的是,经过测试,可以这么简单总结Callable的线程使用情况。

  1. 重写call()方法,并且返回类型就是回调参数的类型
  2. 在处理结束后返回对应的类型对象
  3. 启动该线程(使用自身的call并不是多线程,应该用线程池的submit进行启动调用)
  4. 对回调结果Future进行get()处理(相当于取获取线程的处理结果
    • 这里可以加一步isDone进行判断是否已完成

05 线程池的使用场景

一、取代原本多线程应用到的场景

比如:异步发邮件、心跳线程、或者是对效能优化的一些多线程场景

二、针对executors类生产的几种线程池的常用场景

参考地址

1、newCachedThreadPool

创建一个线程池,如果线程池中的线程数量过大,它可以有效的回收多余的线程,如果线程数不足,那么它可以创建新的线程。

**应用场景:**用于线程数不固定,但是启动时间间隔较长的场景,可以极大的复用线程。

2、newFixedThreadPool /

固定线程数量的线程池,线程数是可以进行控制的,因此我们可以通过控制最大线程来使我们的服务器打到最大的使用率,同事又可以保证及时流量突然增大也不会占用服务器过多的资源。

应用场景:用于需要执行的线程数固定的场景,比如已经明确了n条任务并行的场景

newSingleThreadExecutor 同2

3、⭐️ newScheduledThreadPool (定时线程池)

该线程池支持定时,以及周期性的任务执行,我们可以延迟任务的执行时间,也可以设置一个周期性的时间让任务重复执行。以下特意详解该类型的线程池。

三、定时线程池详解 :ScheduledThreadPool (定时线程池)

1、创建

构造函数如下:

public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory,RejectedExecutionHandler handler) {super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,new DelayedWorkQueue(), threadFactory, handler);
}
  • 和原本的线程池对象ThreadPoolExecutor不同的是,最低参数要求只有一个:corePoolSize( - 池中所保存的线程数(包括空闲线程))。
  • 继承于线程池对象,所以super即使调用ThreadPoolExecutor的构造函数

2、使用

(api:ScheduledExecutorService)schedule方法使用各种延迟创建任务,并返回一个可用于取消或检查执行的任务对象。scheduleAtFixedRatescheduleWithFixedDelay 方法创建并执行某些在取消前一直定期运行的任务。

1)scheduleAtFixedRate (固定速度执行计划

image-20230322160011308

创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;

也就是将在 initialDelay 后开始执行,然后在 initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。

如果任务的任何一个执行遇到异常,则后续执行都会被取消。否则,只能通过执行程序的取消或终止方法来终止该任务。

如果此任务的任何一个执行要花费比其周期更长的时间,则将推迟后续执行,但不会同时执行。写一个测试demo

package com.thread;import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;public class ScheduleThreadDemo {public static void main(String[] args) {ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);System.out.println("开始执行时间:" +DateFormat.getTimeInstance().format(new Date()));
//        executor.scheduleAtFixedRate(new ThreadDemoS() , 1, 5, TimeUnit.SECONDS);executor.scheduleAtFixedRate(new ThreadDemoS() , 1, 5, TimeUnit.SECONDS);}}class ThreadDemoS extends Thread{private final Random random = new Random();private long[] sleeps = {10000l,5000l,1000l};@Overridepublic void run() {long start = System.currentTimeMillis();System.out.println("scheduleAtFixedRate 开始执行时间:" +DateFormat.getTimeInstance().format(new Date()) + "||"+ this.getName());try {Thread.sleep(sleeps[random.nextInt(3)]);} catch (InterruptedException e) {e.printStackTrace();}long end = System.currentTimeMillis();System.out.println("scheduleAtFixedRate 执行花费时间=" + (end - start));System.out.println("scheduleAtFixedRate 执行完成时间:" + DateFormat.getTimeInstance().format(new Date()));System.out.println("======================================");}
}

测试结论如下:

  • 任务执行时间 < 线程池间隔 —— 正常按间隔执行
  • 任务执行时间 > 线程池间隔 —— 按任务间隔执行,也就是当次任务执行后才会执行下一次任务,会打破间隔。

2)scheduleWithFixedDelay ( 固定延期安排

创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。

如果任务的任一执行遇到异常,就会取消后续执行。否则,只能通过执行程序的取消或终止方法来终止该任务。

测试:同样使用上面的例子,测试结果如下:

不难发现,每次任务结束后会固定停止delay秒后再开始下一次任务。和任务的执行时间无关。

06 阿里规范为什么禁止使用使用 Executors 去创建线程池

Executors 返回的线程池对象的弊端前面也已经做了分析,如下:

1) FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2) CachedThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM应该是CPU占用过高。

相关内容

热门资讯

篮球记录表填写方法,如何填写篮... 篮球记录表填写方法目录篮球记录表填写方法如何填写篮球比赛记录表篮球比赛违反体育道德的犯规记录表上怎么...
阳澄湖大闸蟹做法有哪些,阳澄湖... 阳澄湖大闸蟹做法有哪些目录阳澄湖大闸蟹做法有哪些阳澄湖大闸蟹怎么吃?阳澄湖大闸蟹的吃法有哪些?阳澄湖...
广州bw是什么意思,广州9号车... 广州bw是什么意思目录广州bw是什么意思广州9号车房bilibili在哪
广州市内有哪几个区,广州有几个... 广州市内有哪几个区目录广州市内有哪几个区广州有几个区广州市有哪几个区?广州市内一共分几个区?广州市内...
729车牌啥意思(729车牌号... 本篇文章极速百科给大家谈谈729车牌啥意思,以及729车牌号什么意思对应的知识点,希望对各位有所帮助...
MTV中文是什么意思(mtv什... 今天给各位分享MTV中文是什么意思的知识,其中也会对mtv什么含义进行解释,如果能碰巧解决你现在面临...
花王纸尿裤u版e版区别 极速百... 花王纸尿裤u版e版区别目录花王纸尿裤u版e版区别花王纸尿裤u版e版区别花王纸尿裤底部有u字和e字,s...
代表奋斗的成语有哪些,表示坚持... 代表奋斗的成语有哪些目录代表奋斗的成语有哪些表示坚持不懈,努力拼搏的成语有哪些?写出四个表示艰苦奋斗...
奔腾B50的油耗是多少?(奔腾... 本篇文章极速百科给大家谈谈奔腾B50的油耗是多少?,以及奔腾b50的油耗怎么样对应的知识点,希望对各...
鼓励事业低谷男人的话,鼓励事业... 鼓励事业低谷男人的话目录鼓励事业低谷男人的话鼓励事业低谷男人的话 鼓励事业低谷男人的话列述老公事业不...
陪练车哪里找汽车陪练?驾驶陪练... 本篇文章极速百科给大家谈谈陪练车哪里找汽车陪练?驾驶陪练价格表,以及汽车陪练去哪个平台对应的知识点,...
四川音乐学院是一本吗,四川音乐... 四川音乐学院是一本吗目录川音是一本还是二本?四川音乐学院是一本吗,是二本吗? 川音是一本还是二本?二...
海信电视启动不起来怎么办,海信... 海信电视启动不起来怎么办目录海信电视启动不起来怎么办海信电视机启动不了怎么办海信液晶电视无法正常开机...
豆豆鞋配什么裤子好看,豆豆鞋穿... 豆豆鞋配什么裤子好看目录豆豆鞋配什么裤子好看豆豆鞋穿什么裤子搭配好看那个红色豆豆鞋搭配什么裤子好看豆...
五大洲四大洋(五大洲四大洋八方... 本篇文章极速百科给大家谈谈五大洲四大洋,以及五大洲四大洋八方聚对应的知识点,希望对各位有所帮助,不要...
拌面的酱汁怎么做好吃,拌面酱怎... 拌面的酱汁怎么做好吃目录拌面的酱汁怎么做好吃拌面酱怎么做简单又好吃拌面的拌酱怎么做好吃拌面的酱料怎么...
油价调整消息:明日国内油价或将... 今天给各位分享油价调整消息:明日国内油价或将下调,车主喜讯!的知识,其中也会对明日油价上涨进行解释,...
汽车汽油泵损坏有什么症状,汽油... 汽车汽油泵损坏有什么症状目录汽车汽油泵损坏有什么症状汽油泵坏了通常有什么异常现象汽油泵坏了有什么症状...
狗狗为什么跟疯了似的来回跑 极... 狗狗为什么跟疯了似的来回跑目录狗狗为什么跟疯了似的来回跑狗狗为什么跟疯了似的来回跑小狗有时像疯了一样...
在支付宝上怎么贷款,支付宝贷款... 在支付宝上怎么贷款目录在支付宝上怎么贷款支付宝贷款在哪里 支付宝贷款怎么申请在支付宝上怎么贷款在支付...
赛艇运动起源于哪个国家,奥运会... 赛艇运动起源于哪个国家目录赛艇运动起源于哪个国家奥运会传统比赛项目“赛艇”起源于欧洲哪个国家?赛艇运...
时空猎人首充号为什么充值会打折... 时空猎人首充号为什么充值会打折目录时空猎人首充号为什么充值会打折安卓手游充值折扣平台安卓手游充值折扣...
自助无人洗车机多少钱一台(24... 今天给各位分享自助无人洗车机多少钱一台的知识,其中也会对24小时自助共享洗车店要多少钱进行解释,如果...
白粥的做法,白粥的做法,白粥怎... 白粥的做法目录白粥的做法白粥的做法,白粥怎么做好吃,白粥的家常做法怎样煮白粥啊?白粥的做法 制...
包含抖音很火的叫爸爸是什么梗的... 今天给各位分享抖音很火的叫爸爸是什么梗的知识,其中也会对进行解释,如果能碰巧解决你现在面临的问题,别...
深圳欢乐谷的门票是怎么收费得,... 深圳欢乐谷的门票是怎么收费得目录深圳欢乐谷的门票是怎么收费得2022深圳欢乐谷万圣节老人免费吗?深圳...
阴阳师青蛙瓷器哪里多 极速百科... 阴阳师青蛙瓷器哪里多目录阴阳师青蛙瓷器哪里多阴阳师青蛙瓷器哪里多阴阳师的青蛙瓷器在哪个副本出现青蛙瓷...
一个G流量是多少MB流量,手机... 一个G流量是多少MB流量目录一个G流量是多少MB流量1G上网流量是多少MB一个G的流量等于多少MB一...
道台是什么官职 极速百科网 极... 道台是什么官职目录道台是什么官职道台是什么官职在清朝道台是个什么官“道台”是官职名吗道台是什么官职 ...
描写可怕的笑的成语笑得阴险可怕... 描写可怕的笑的成语笑得阴险可怕目录描写可怕的笑的成语笑得阴险可怕描写阴险的笑的成语表示阴险的笑有哪些...