之前的线程池已实现了基本的功能:运行每一个线程,而且测试了一下,大约速度是ThreadPoolExecutor的1.5倍(当然,这是有充分的理由的,后文会提到)
之后的版本将准备是实现“优雅退出”和优化(非阻塞)空闲线程队列了,这个步骤想了很久,发现了很多的问题(包括准备的实现方法也在这里列一下):
1.初步构思了几个方法
- void shutDown():该方法将让池不再接受任务,但会将现有的任务全部运行结束后停止
- List<Runnable> shutDownNow():该方法会interrupt线程,然后收集未调用的任务,并进一步回收已调用的任务(“已调用”不包括运行中的,因为运行中无法人为停止,只包括已下发到线程,但线程尚未执行的),然后将这些任务返回
- List<Runnable> shutDownAndWait():跟shutDownNow类似,区别是会等待至线程池完全关闭
2.线程及池的状态,一开始很傻气地用了boolean去实现,比如:isShutDown,isRunning等,而这些变量又无法避免地被声明为volatile,我们知道,对volatile的写和读都会比对线程的内部变量的访问占用更多的消耗,与其用多个boolean,不如用一个runState,只需要判断大小就可以了,但对volatile变量的访问只有一次,而判断大小所占用的cpu时间几乎可以忽略不计
这也是一个原则性的问题了,这里记录下来,算是积累把:
表示一个东西的状态(多个状态),尽量还是使用int,如果有同步问题,则用volatile,CAS对于单个变量的控制还是很有效的
3.怎么回收已经下发,但未执行的任务?这就牵扯到另一件事了,需要获取线程的状态(也可以是任务的执行状态,因为这里目标是回收未执行的任务),这就不得不引入了更多的volatile变量,然后这个状态变量,还必须在每次执行任务的时候进行修改,就变成了一个很大的消耗了(这是不能容忍的)
注:这里不能用Thread.getState()来获得线程的状态,该状态是用以系统监控的,并不是用来控制逻辑的(也就是说它不准确)
4.空闲线程队列如果打算重新实现,这又不可避免地需要加入更多的volatile和与程序逻辑相关的happen-before关系了,这会让本来的程序逻辑变得模糊,而且出现问题的几率变得非常大
5.因为要对每个线程进行interrupt,所以又不得不再维护一个线程队列,所以又不得不
……
发现实现当初的目标越来越麻烦了
带着这些问题,最终还是去看了ThreadPoolExecutor的实现,看看大师是怎么做的
发现ThreadPoolExecutor的性能优化只做在了减少锁的范围上,使用的是ReentrantLock,而大部分的参数都使用了volatile,目的只是为了保证可见性,几乎没有出现利用happen-before规则的代码,也就是说,在ThreadPoolExecutor的实现上,优化程度只是在锁的级别上,并没有考虑进一步的优化。而查看类的作者,发现是Doug Lea
回头想想,的确,线程池是不同于AQS的框架的,因为该线程池的实现更注重的是稳定,可复用性,灵活性,安全性。一般说来,线程池调用时间与真正任务的执行时间不是一个数量级的,所以又何必去太计较那点性能的消耗呢?
因此之前所说的“运行速度是ThreadPoolExecutor的1.5倍”的原因也就出来了:任务太简单了,所以大大提高了任务调用时间所占的比例,在现实生活中应该是很少出现这样的情况的。而又说明了另一点,每样功能的实现都是要消耗性能的(特指在并发中),所以在写要求高性能的相关的实现时,应该尽量将需求给落实了,减少功能的需求,才能得到更好的性能
这里再写一下看ThreadPoolExecutor的收获
1.它可以让你注册一个RejectedExecutionHandler,来控制你的下发被拒绝的任务
2.它允许你注册一个threadFactory,来创建线程(这样可以自由地订制自己所需要的线程)
3.里面有一个方法很值得学习的:
我们在正常逻辑中,会有一些常见场景和例外场景(像可能常见场景不需要加锁,但例外场景需要),例外场景应该用尽量少的变量来判断是否进入,而常见场景应该与例外场景完全分离(即使有方法可以重用)
举个例子
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
if (runState == RUNNING && workQueue.offer(command)) {//常见场景
if (runState != RUNNING || poolSize == 0)//判断是否进入例外场景
ensureQueuedTaskHandled(command);//例外场景,有加锁动作
}
else if (!addIfUnderMaximumPoolSize(command))
reject(command); // is shutdown or saturated
}
}
4.类中定义的变量,都用注释说明了被哪一个锁守卫的(也就是说使用上,必须在在这个锁lock状态下才允许修改),这让我想起了《java concurrency in practice》中对使用并发相关annotation的建议,
之后再去找找网上有没有提供这个库,如果在编译期就能根据annotation检查错误那就更好了
5.使用了BlockingQueue接口的原有定义来实现:为了减少线程的切换消耗,每个线程在任务队列空了之后,再进行一定时间的等待。(虽然没有知道明确地定义,但根据之前阅读的一些文章,大概推断jvm的实现上,如果时间很短,是不会将线程切换出去的,而是用一个循环机制来实现的)
6.线程的等待与唤醒也是通过BlockingQueue来实现的,ThreadPoolExecutor只维护池的状态,并不会维护线程的任何状态信息,线程的基本动作是由BlockingQueue控制的,这其实在一定的程度上,将并发的复杂度降低了,让ThreadPoolExecutor的功能变得更简单,更容易理解,更容易扩展了
7.优化关键步骤:像线程池,最关键的步骤(也就是被调用次数最多,整个实现中消耗最大的部分)就是getTask,execute,和runTask方法了,在其他方法中,可以没必要去追求什么效率,因为调用次数太少了,像shutdown(就一次),或者addThread,都是很少的,所以为了避免错误,我们可以考虑加锁。但对于关键步骤,我们应该尽可能地优化,少加锁或者不加锁,像3所说的小技巧
8.ThreadPoolExecutor也并没有回收已下发到线程中的任务,而是将它运行完(也就是在getTask动作跟runTask动作之间,并没有什么机制来保证能及时发现关闭事件)
总的来说,之前所制定的计划——实现一个高效而功能强大的线程池——还是有点问题。
就算是JDK的ThreadPoolExecutor,也做不到这一点。每个功能的实现是应该有具体需求的,是优先考虑性能还是其他,都是要有依据的,优先一方面就得牺牲另一方面。像JDK的ThreadPoolExecutor也是为了实现可复用,稳定,全功能,牺牲了一定程度的性能。
分享到:
相关推荐
Java并发编程中的线程池是提高系统效率的关键工具,它解决了频繁创建和销毁线程的问题。线程池通过复用已存在的线程来处理任务,从而避免了每次任务执行完毕后销毁线程的开销。在Java中,线程池的核心实现是`java....
Java并发编程中的JUC线程池是Java程序员必须掌握的关键技术之一,它允许开发者高效地管理并发执行的任务,充分利用多核处理器的性能。线程池的出现解决了在并发环境中线程创建、销毁带来的开销,提高了系统资源的...
线程池是Java多线程编程中的重要概念,它是一种管理线程的机制,通过池化技术有效地管理和控制线程的生命周期,以提高系统资源的...通过持续学习和实践,我们可以更好地驾驭线程池,提升系统的并发处理能力和响应速度。
Java8并行流中自定义线程池操作示例 Java8并行流中自定义线程池操作示例主要介绍了Java8并行流中自定义线程池操作,结合实例形式分析了并行流的相关概念、定义及自定义线程池的相关操作技巧。 1. 概览 Java8引入了...
Java并发编程实践中的线程池是一个关键的概念,它在多线程编程中扮演着至关重要的角色,有效地管理和调度线程资源,以提高系统的性能和效率。线程池通过复用已存在的线程来减少线程的创建和销毁开销,避免了频繁的上...
线程池是Java并发编程的核心技术之一,它通过复用一组预创建的线程来执行任务,从而减少了创建和销毁线程的开销,提高了系统的响应速度和处理能力。 #### 线程池的作用 线程池可以显著提高多线程应用的性能和效率...
《Java并发编程:设计原则与模式(第二版)》是一本深入探讨Java多线程编程技术的权威著作。这本书详细阐述了在Java平台中进行高效并发处理的关键概念、设计原则和实用模式。以下是对该书内容的一些核心知识点的概述...
在Java编程中,线程池是一种管理线程资源的有效方式,它可以提高...在阅读《聊聊并发(3)Java线程池的分析和使用》这份文档时,你可以学习到更多关于线程池的实践技巧和案例分析,这对于提升Java开发能力大有裨益。
Java 并发学习笔记: 进程和线程, 并发理论, 并发关键字, Lock 体系, 原子操作类, 发容器 & 并发工具, 线程池, 并发实践 Java是一种面向对象的编程语言,由Sun Microsystems于1995年推出。它是一种跨平台的...
Java并发编程是Java开发中的重要领域,特别是在大型系统和服务器端应用中,高效地利用多核处理器资源,实现高并发性能至关重要。...通过深入学习和实践,开发者可以更好地驾驭Java并发,提升程序的性能和稳定性。
Java线程池是一种高效管理线程资源的工具,它能够帮助开发者有效地控制并调度线程,从而提升系统性能,减少系统资源的浪费。...通过深入学习和实践,我们可以更好地利用线程池来优化我们的Java应用程序。
Java并发编程是提升系统性能的关键,理解并熟练运用线程池可以有效地管理线程资源,提高系统的响应速度和并发能力。在实际项目中,合理设计和配置线程池,结合具体业务需求,能够使程序运行更加稳定、高效。通过阅读...
Java线程池是一种高级的多线程处理框架,它是Java并发编程中非常重要的一个组件。线程池的原理和实现涉及到操作系统调度、内存管理和并发控制等多个方面。理解线程池的工作原理有助于优化程序性能,避免过度创建和...
《Java并发编程的艺术》这本书是Java开发者深入理解并发编程的重要参考书籍。这本书全面地介绍了Java平台上的并发和多线程编程技术,旨在帮助开发者解决在实际工作中遇到的并发问题,提高程序的性能和可伸缩性。 ...
《Java并发编程实战》是Java并发编程领域的一本经典著作,它深入浅出地介绍了如何在Java平台上进行高效的多线程编程。这本书的源码提供了丰富的示例,可以帮助读者更好地理解书中的理论知识并将其应用到实际项目中。...
通过学习《Java并发编程:设计原则与模式(第二版)》,开发者可以掌握Java并发编程的核心技术和最佳实践,提升在多线程环境下的编程能力。书中的实例和实战经验将帮助读者在实际开发中避免常见的并发陷阱,编写出...
### JAVA线程池原理及几种...综上所述,线程池是Java并发编程中的一个重要概念,它可以帮助开发者更高效地管理线程资源,提高应用程序的性能。理解线程池的工作原理及其配置细节,对于构建高性能的并发应用至关重要。
Java线程池封装是Java并发编程中重要的一环,合理的线程池配置和封装能显著提升程序的性能和稳定性。理解线程池的工作原理,根据业务需求选择合适的参数,以及正确处理拒绝策略,都是实现高效并发处理的关键。在实际...
通过深入学习"Java并发编程与实践"文档,开发者能够提升自己在高并发环境下的编程能力,设计出更加健壮和高效的Java应用程序。这份资料对于理解Java并发原理、优化并发代码和解决并发问题具有极大的价值。
Java中的线程池是多线程编程中一种高效、可管理的执行机制。它通过预先创建并维护一组线程,避免了频繁地创建和销毁线程带来的开销,从而提高了程序的性能和响应速度。线程池的核心概念包括以下几个方面: 1. **...