深入源码,深度解析Java 线程池的实现原理(1)

本文详细介绍了Java线程池的关键参数、常用的阻塞队列及其操作,包括线程池工厂和四种拒绝策略。此外,还对比了execute和submit方法,并讨论了线程池的优雅关闭方式和合理参数设置策略。
摘要由CSDN通过智能技术生成

主要参数就是下面这几个:

  • corePoolSize:线程池中的核心线程数,包括空闲线程,也就是核心线程数的大小;

  • maximumPoolSize:线程池中允许的最多的线程数,也就是说线程池中的线程数是不可能超过该值的;

  • keepAliveTime:当线程池中的线程数大于 corePoolSize 的时候,在超过指定的时间之后就会将多出 corePoolSize 的的空闲的线程从线程池中删除;

  • unit:keepAliveTime 参数的单位(常用的秒为单位);

  • workQueue:用于保存任务的队列,此队列仅保持由 executor 方法提交的任务 Runnable 任务;

  • threadFactory:线程池工厂,他主要是为了给线程起一个标识。也就是为线程起一个具有意义的名称;

  • handler:拒绝策略

阻塞队列

workQueue 有多种选择,在 JDK 中一共提供了 7 中阻塞对列,分别为:

  1. ArrayBlockingQueue : 一个由数组结构组成的有界阻塞队列。 此队列按照先进先出(FIFO)的原则对元素进行排序。默认情况下不保证访问者公平地访问队列 ,所谓公平访问队列是指阻塞的线程,可按照阻塞的先后顺序访问队列。非公平性是对先等待的线程是不公平的,当队列可用时,阻塞的线程都可以竞争访问队列的资格。

  2. LinkedBlockingQueue : 一个由链表结构组成的有界阻塞队列。 此队列的默认和最大长度为Integer.MAX_VALUE。 此队列按照先进先出的原则对元素进行排序。

  3. PriorityBlockingQueue : 一个支持优先级排序的无界阻塞队列。 (虽然此队列逻辑上是无界的,但是资源被耗尽时试图执行 add 操作也将失败,导致 OutOfMemoryError)

  4. DelayQueue: 一个使用优先级队列实现的无界阻塞队列。 元素的一个无界阻塞队列,只有在延迟期满时才能从中提取元素

  5. SynchronousQueue: 一个不存储元素的阻塞队列。 一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。(SynchronousQueue 该队列不保存元素)

  6. LinkedTransferQueue: 一个由链表结构组成的无界阻塞队列。 相对于其他阻塞队列LinkedTransferQueue多了tryTransfer和transfer方法。

  7. LinkedBlockingDeque: 一个由链表结构组成的双向阻塞队列。 是一个由链表结构组成的双向阻塞队列

在以上的7个队列中,线程池中常用的是ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue

队列中的常用的方法如下:

| 类型 | 方法 | 含义 | 特点 |

| — | — | — | — |

| 抛异常 | add | 添加一个元素 | 如果队列满,抛出异常 IllegalStateException |

| 抛异常 | remove | 返回并删除队列的头节点 | 如果队列空,抛出异常 NoSuchElementException |

| 抛异常 | element | 返回队列头节点 | 如果队列空,抛出异常 NoSuchElementException |

| 不抛异常,但是不阻塞 | offer | 添加一个元素 | 添加成功,返回 true,添加失败,返回 false |

| 不抛异常,但是不阻塞 | poll | 返回并删除队列的头节点 | 如果队列空,返回 null |

| 不抛异常,但是不阻塞 | peek | 返回队列头节点 | 如果队列空,返回 null |

| 阻塞 | put | 添加一个元素 | 如果队列满,阻塞 |

| 阻塞 | take | 返回并删除队列的头节点 | 如果队列空,阻塞 |

关于阻塞队列,介绍到这里也就基本差不多了。

线程池工厂

线程池工厂,就像上面已经介绍的,目的是为了给线程起一个有意义的名字。用起来也非常的简单,只需要实现ThreadFactory接口即可

public class CustomThreadFactory implements ThreadFactory {

@Override

public Thread newThread(Runnable r) {

Thread thread = new Thread®;

thread.setName(“我是你们自己定义的线程名称”);

return thread;

}

}

具体的使用就不去废话了。

拒绝策略

线程池有四种默认的拒绝策略,分别为:

  1. AbortPolicy:这是线程池默认的拒绝策略,在任务不能再提交的时候,抛出异常,及时反馈程序运行状态。如果是比较关键的业务,推荐使用此拒绝策略,这样子在系统不能承载更大的并发量的时候,能够及时的通过异常发现;

  2. DiscardPolicy:丢弃任务,但是不抛出异常。如果线程队列已满,则后续提交的任务都会被丢弃,且是静默丢弃。这玩意不建议使用;

  3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。这玩意不建议使用;

  4. CallerRunsPolicy:如果任务添加失败,那么主线程就会自己调用执行器中的 executor 方法来执行该任务。这玩意不建议使用;

也就是说关于线程池的拒绝策略,最好使用默认的。这样能够及时发现异常。如果上面的都不能满足你的需求,你也可以自定义拒绝策略,只需要实现 RejectedExecutionHandler 接口即可

public class CustomRejection implements RejectedExecutionHandler {

@Override

public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {

System.out.println(“你自己想怎么处理就怎么处理”);

}

}

看到这里,我们再来画一张图来总结和概括下线程池的执行示意图:

详细的执行过程全部在图中说明了。

提交任务到线程池


在 java 中,有两个方法可以将任务提交到线程池,分别是submitexecute

execute 方法

execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。

void execute(Runnable command);

通过以下代码可知 execute() 方法输入的任务是一个Runnable类的实例。

executorService.execute(()->{

System.out.println(“ThreadPoolDemo.execute”);

});

submit 方法

submit()方法用于提交需要返回值的任务。

Future<?> submit(Runnable task);

线程池会返回一个future类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。

Future<?> submit = executorService.submit(() -> {

System.out.println(“ThreadPoolDemo.submit”);

});

关闭线程池


其实,如果优雅的关闭线程池是一个令人头疼的问题,线程开启是简单的,但是想要停止却不是那么容易的。通常而言, 大部分程序员都是使用 jdk 提供的两个方法来关闭线程池,他们分别是:shutdownshutdownNow

通过调用线程池的 shutdownshutdownNow 方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断线程(PS:中断,仅仅是给线程打上一个标记,并不是代表这个线程停止了,如果线程不响应中断,那么这个标记将毫无作用),所以无法响应中断的任务可能永远无法终止。

但是它们存在一定的区别,shutdownNow首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程。

只要调用了这两个关闭方法中的任意一个,isShutdown 方法就会返回 true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed方法会返回 true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定,通常调用 shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用 shutdownNow 方法。

这里推荐使用稳妥的 shutdownNow 来关闭线程池,至于更优雅的方式我会在以后的并发编程设计模式中的两阶段终止模式中会再次详细介绍。

合理的参数


为什么叫合理的参数,那不合理的参数是什么样子的?在我们创建线程池的时候,里面的参数该如何设置才能称之为合理呢?其实这是有一定的依据的,我们先来看一下以下的创建的方式:

ExecutorService executorService = new ThreadPoolExecutor(5,

5,

5,

TimeUnit.SECONDS,

new ArrayBlockingQueue<>(5),

r -> {

Thread thread = new Thread®;

thread.setName(“线程池原理讲解”);

return thread;

});

你说他合理不合理?我也不知道,因为我们没有参考的依据,在实际的开发中,我们需要根据任务的性质(IO是否频繁?)来决定我们创建的核心的线程数的大小,实际上可以从以下的一个角度来分析:

  • 任务的性质:CPU密集型任务、IO密集型任务和混合型任务;

  • 任务的优先级:高、中和低;

  • 任务的执行时间:长、中和短;

  • 任务的依赖性:是否依赖其他系统资源,如数据库连接;

性质不同的任务可以用不同规模的线程池分开处理。分为CPU密集型和IO密集型

CPU密集型任务应配置尽可能小的线程,如配置 Ncpu+1个线程的线程池。(可以通过Runtime.getRuntime().availableProcessors()来获取CPU物理核数)

IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 2*Ncpu

混合型的任务,如果可以拆分,将其拆分成一个CPU密集型任务一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐量将高于串行执行的吞吐量。

如果这两个任务执行时间相差太大,则没必要进行分解。可以通过 Runtime.getRuntime().availableProcessors() 方法获得当前设备的CPU个数。

优先级不同的任务可以使用优先级队列 PriorityBlockingQueue来处理。它可以让优先级高的任务先执行(注意:如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

小编在这里分享些我自己平时的学习资料,由于篇幅限制,pdf文档的详解资料太全面,细节内容实在太多啦,所以只把部分知识点截图出来粗略的介绍,每个小节点里面都有更细化的内容!

程序员代码面试指南 IT名企算法与数据结构题目最优解

这是” 本程序员面试宝典!书中对IT名企代码面试各类题目的最优解进行了总结,并提供了相关代码实现。针对当前程序员面试缺乏权威题目汇总这一-痛点, 本书选取将近200道真实出现过的经典代码面试题,帮助广“大程序员的面试准备做到万无一失。 “刷”完本书后,你就是“题王”!

image.png

《TCP-IP协议组(第4版)》

本书是介绍TCP/IP协议族的经典图书的最新版本。本书自第1版出版以来,就广受读者欢迎。

本书最新版进行」护元,以体境计算机网络技不的最新发展,全书古有七大部分共30草和7个附录:第一部分介绍一些基本概念和基础底层技术:第二部分介绍网络层协议:第三部分介绍运输层协议;第四部分介绍应用层协议:第五部分介绍下一代协议,即IPv6协议:第六部分介绍网络安全问题:第七部分给出了7个附录。

image.png

Java开发手册(嵩山版)

这个不用多说了,阿里的开发手册,每次更新我都会看,这是8月初最新更新的**(嵩山版)**

image.png

MySQL 8从入门到精通

本书主要内容包括MySQL的安装与配置、数据库的创建、数据表的创建、数据类型和运算符、MySQL 函数、查询数据、数据表的操作(插入、更新与删除数据)、索引、存储过程和函数、视图、触发器、用户管理、数据备份与还原、MySQL 日志、性能优化、MySQL Repl ication、MySQL Workbench、 MySQL Utilities、 MySQL Proxy、PHP操作MySQL数据库和PDO数据库抽象类库等。最后通过3个综合案例的数据库设计,进步讲述 MySQL在实际工作中的应用。

image.png

Spring5高级编程(第5版)

本书涵盖Spring 5的所有内容,如果想要充分利用这一领先的企业级 Java应用程序开发框架的强大功能,本书是最全面的Spring参考和实用指南。

本书第5版涵盖核心的Spring及其与其他领先的Java技术(比如Hibemate JPA 2.Tls、Thymeleaf和WebSocket)的集成。本书的重点是介绍如何使用Java配置类、lambda 表达式、Spring Boot以及反应式编程。同时,将与企业级应用程序开发人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。

image.png

JAVA核心知识点+1000道 互联网Java工程师面试题

image.png

image.png

企业IT架构转型之道 阿里巴巴中台战略思想与架构实战

本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。

image.png
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
人员分享一些见解和实际经验,包括远程处理、事务、Web 和表示层,等等。

[外链图片转存中…(img-p5h81XvD-1713748165063)]

JAVA核心知识点+1000道 互联网Java工程师面试题

[外链图片转存中…(img-eaZQ4QeY-1713748165063)]

[外链图片转存中…(img-kD6KgyE3-1713748165063)]

企业IT架构转型之道 阿里巴巴中台战略思想与架构实战

本书讲述了阿里巴巴的技术发展史,同时也是-部互联网技 术架构的实践与发展史。

[外链图片转存中…(img-4FcnHYVt-1713748165063)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 14
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值