深入源码,深入剖析Java线程池的实现原理其实操作系统的线程在执行,占用着一定的系统资源,如CPU、内存、磁盘、网络等。因此,如何高效地使用这些资源是程序员在编写代码时努力的一个方向。本文要说的线程池是一种优化CPU利用率的手段。线程池,百度百科是这样解释的:线程池是多线程处理的一种形式。在处理过程中,将任务添加到队列中,然后创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程使用默认堆栈大小,以默认优先级运行,并且位于多线程单元中。如果一个线程在托管代码中空闲(比如等待一个事件),线程池将插入另一个工作线程来保持所有处理器忙碌。如果所有的线程池线程一直处于忙碌状态,但队列中有待处理的工作,线程池会在一段时间后创建另一个工作线程,但线程数永远不会超过最大值。超过最大值的线程可以排队,但在其他线程完成之前它们不会启动。线程池其实就是一个维护了很多线程的池。像这样的池化技术还有很多,比如:HttpClient连接池、数据库连接池、内存池等。线程池的优点Java并发编程框架中的线程池是应用最广泛的技术。几乎所有需要异步或并发执行任务的程序都可以使用线程池。在开发过程中,合理使用线程池至少可以带来以下四点好处。第一:减少资源消耗。通过复用创建的线程,减少线程创建和销毁带来的消耗;第二:提高响应速度。当任务到达时,可以立即执行任务,无需等待创建线程;第三:提高线程的可管理性。线程是稀缺资源。如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以进行统一分配、调优和监控。第四:提供更强大的功能。比如延迟定时线程池;线程池的实现原理当一个任务提交给线程池时,线程池是如何处理这个任务的呢?我们来看看它的主要处理流程。先看下图,后面我们一步步讲解。当用户向线程池提交任务时,线程池是这样执行的:①首先判断核心线程数是否已满,如果没有,则创建线程执行任务;否则,请看下一步②如果线程池中的核心线程数已满,则继续判断任务队列是否已满,如果没有,则将任务放入任务队列;否则,请看下一步③如果任务队列已满,则判断线程池是否已满,如果未满,则创建线程执行该任务;否则,请看下一步;④如果线程池已满,则根据拒绝策略进行相应处理;以上四个步骤其实已经说明了线程池的执行原理。看不懂没关系,我们一步步往下看,上面提到的线程池的专有名词会详细介绍。在我们平时的开发中,线程池的使用基本都是基于ThreadPoolExexutor类,它的继承体系是这样的:image-20210322133058425既然说在使用上都是基于ThreadPoolExexutor,那我们就着重分析这个班级。至于他的构造系统中的其他类或者界面中的属性,我这里就不截图了,完全没有必要。如果实在想看的话,直接打开代码看看就可以了。ThreadPoolExecutor在《阿里巴巴 java 开发手册》中指出线程资源必须通过线程池提供,不允许创建在应用程序中显示的线程。一方面,线程的创建更加规范,可以合理控制线程的开启数量;细节管理交给线程池,优化了资源的开销。原文描述如下:ThreadPoolExecutor类中提供了四个构造函数,但是在他的四个构造函数中,最后会调用同一个构造函数,但是在其他三个构造函数中,如果有些参数不传ThreadPoolExecutor会帮你使用默认参数。那么,我们直接看参数完整的构造函数,彻底分析一下里面的参数。publicclassThreadPoolExecutorextendsAbstractExecutorService{......publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler){if(corePoolSize<0||maximumPoolSize<=0||maximumPoolSize{System.out.println("ThreadPoolDemo.execute");});submit方法submit()方法用于提交需要返回值的任务Future>submit(Runnabletask);线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否是执行成功,可以得到返回值thr未来的get()方法。get()方法会阻塞当前线程等待任务完成,而使用get(longtimeout,TimeUnitunit)方法会阻塞当前线程一段时间,然后立即返回。这个时候,任务可能还没有完成。Future>submit=executorService.submit(()->{System.out.println("ThreadPoolDemo.submit");});关闭线程池事实上,如果优雅地关闭线程池是一件令人头疼的事情,启动一个线程很容易,但停止它就没那么容易了。一般来说,大多数程序员使用jdk提供的两种方法来关闭线程池,它们是:shutdown或shutdownNow;通过调用线程池的shutdown或shutdownNow方法关闭线程池。它们的原理是遍历线程池中的工作线程,然后一个一个调用该线程的interrupt方法来中断该线程(PS:interrupt,只是在线程上打个标记,不代表该线程是stopped,如果线程没有响应中断,那么这个标志将没有作用),所以不能响应中断的任务可能永远不会终止。但它们之间存在一些差异。shutdownNow首先将线程池的状态设置为STOP,然后尝试停止所有正在执行或挂起任务的线程,并返回等待执行的任务列表,而shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有未执行任务的线程。每当调用这两种关闭方法中的任何一种时,isShutdown方法都会返回true。当所有任务都关闭时,表示线程池关闭成功,调用isTerminaed方法会返回true。至于调用什么方法关闭线程池,应该根据提交到线程池的任务的特点来决定。通常调用shutdown方法关闭线程池。如果不需要执行任务,可以调用shutdownNow方法。推荐使用安全的shutdownNow关闭线程池。至于更优雅的方式,我会在以后并发编程设计模式中的两阶段终止模式中再详细介绍。合理的参数为什么叫合理的参数,不合理的参数长什么样子?我们在创建线程池的时候,里面的参数怎么设置才能调用的合理呢?其实,这是有一定依据的。我们先看下面的创建方法:ExecutorServiceexecutorService=newThreadPoolExecutor(5,5,5,TimeUnit.SECONDS,newArrayBlockingQueue<>(5),r->{Threadthread=newThread(r);thread.setName("原理解释线程池");returnthread;}??);你觉得合理不合理?我不知道,因为我们没有参考依据。在实际开发中,我们需要根据任务的性质(IO频繁吗?)来决定我们创建的核心线程数,其实从以下几个角度来分析:任务的性质:CPU密集型任务,IO密集型任务和混合型任务;任务的优先级:高、中、低;任务的执行时间:长、中、短;任务的依赖性:是否依赖其他系统资源,如数据库连接;不同性质的任务可以由不同大小的线程池分别处理。分为CPU密集型和IO密集型。CPU密集型任务应该配置尽可能小的线程,比如配置Ncpu+1个线程的线程池。(CPU物理核心数可以通过Runtime.getRuntime().availableProcessors()获得)IO密集型任务线程并不是一直在执行任务,所以应该配置尽可能多的线程,比如2*Ncpu。如果一个混合任务可以拆分,那就拆分成一个CPU密集型任务和一个IO密集型任务。只要这两个任务的执行时间相差不大,分解后的吞吐量会比串行任务高。行执行的吞吐量。如果两个任务之间的执行时间相差太大,则不需要分解。可以通过Runtime.getRuntime().availableProcessors()方法获取当前设备的CPU数量。可以使用PriorityBlockingQueue处理具有不同优先级的任务。它允许优先级高的任务先执行(注意:如果优先级高的任务一直提交到队列中,那么优先级低的任务可能永远不会执行)不同执行时间的任务可以交给不同大小的线程池来处理进程,或者您可以使用优先级队列让执行时间短的任务首先执行。依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果。等待时间越长,CPU空闲时间越长,所以线程数应该设置的大一些,这样才能更好的利用CPU。建议使用有界队列。有界队列可以增加系统的稳定性和预警能力,可以根据需要设置大些。OOM的方式是由于提交的任务太多导致的。小结本文主要介绍线程池的实现原理和一些使用技巧。在实际开发中,线程池可以说是略微进阶程序员的必备技能。所以掌握线程池的技术也是重中之重!另外,理解了原理之后,在使用线程池的时候要格外小心。阿里巴巴Java开发手册中提到,禁止使用Executors直接创建线程池,因为可能会导致OOM(OutOfMemory,内存溢出),具体原因及备选参考:threadpoolinJava,doyoureally知道怎么用吗?