当前位置: 首页 > 后端技术 > Java

【高并发】关于线程池和ThreadPoolExecutor类解析我不得不说的

时间:2023-04-01 21:10:05 Java

大家好,我是冰河~~今天简单说说线程池中的ThreadPoolExecutor类。好了,话不多说,开始进入今天的主题。一、抛砖引玉既然Java支持多线程执行相应的任务,为什么JDK1.5要提供线程池技术呢?这个题大家可以自己补,多动点脑筋肯定是没有坏处的,哈哈哈。..说起Java中的线程池技术,在很多框架和异步处理中间件中都有涉及,其性能经受住了长期的考验。可以说Java的线程池技术是Java的核心技术之一。在Java的高并发领域,Java的线程池技术是永远绕不开的话题。既然Java的线程池技术这么重要(怎么能说这么重要呢,挺重要的,那家伙又老又重要,哈哈哈),那么,这篇文章,我们就简单说说线程池和ThreadPoolExecutor班级。关于线程池中的各种技术细节以及ThreadPoolExecutor的底层原理和源码分析,我们将在【高并发专题】专栏进行深入分析。简介:本文是高并发中线程池的开篇,暂时不深入讲解,而是让大家从整体上了解线程池中的一个核心类——ThreadPoolExecutor,关于ThreadPoolExecutor的底层原理和源码实现,以及线程池中其他技术细节的底层原理和源码实现,我们会在【高并发专题】的下一篇中死锁。二、Thread直接创建线程的缺点(1)每次newThread都会创建一个新的对象,性能较差。(2)线程缺乏统一管理,可能有无限的新线程,相互竞争,可能占用太多系统资源,导致崩溃或OOM。(3)更多的功能缺失,比如更多的执行,周期执行,线程中断。(4)其他的缺点,大家可以自己脑补,多动脑筋也无妨,哈哈。3.线程池的好处(1)复用现有线程,降低对象创建和消亡的成本,性能好。(2)可以有效控制线程的最大并发数,提高系统资源的利用率,同时避免过度的资源竞争,避免阻塞。(3)提供定时执行、周期执行、单线程、并发数控制等功能。(4)提供支持线程池监控的方法,可以实时监控线程池的资源情况。(5)其他福利,大家可以自己脑补。多动脑筋也无妨,哈哈。四、线程池1、线程池类结构关系线程池中一些接口和类的结构关系如下图所示。这些接口和类的底层原理和源码后面会讲到。2、创建线程池的常用类——ExecutorsExecutors.newCachedThreadPool:创建可缓存的线程池。如果线程池的大小超过需要,可以灵活回收空闲线程。如果没有可回收的线程,则创建一个新的线程Executors.newFixedThreadPool:创建一个可以控制最大并发线程数的定长线程池。多余的线程会在队列中等待Executors.newScheduledThreadPool:创建一个支持定时和周期性任务执行的定长线程池Executors.newSingleThreadExecutor:创建一个单线程线程池,使用唯一的工作线程来执行任务,保证所有任务按指定顺序执行(先进先出或优先级)Executors.newSingleThreadScheduledExecutor:创建单线程线程池,支持定时,周期性任务执行Executors.newWorkStealingPool:创建并行级别为3的work-stealing线程池线程池实例的几种状态Running:运行状态,可以接收新提交的任务,也可以处理阻塞队列中的任务Shutdown:关闭状态,不能再接收新提交的任务,但是可以处理已经保存在阻塞队列中的任务队列。当线程池处于Running状态时,调用shutdown()方法会导致线程池进入该状态Stop:不能接收新的任务,也不能处理已经保存在阻塞队列中的任务,会中断该线程正在处理任务。如果线程池处于Running或Shutdown状态,调用shutdownNow()方法会使线程池进入该状态。Tidying:如果所有任务都已经终止,则有效当线程数为0(阻塞队列为空,线程池工作线程数为0)时,线程池将进入该状态。Terminated:处于Tidying状态的线程池调用terminated()方法,线程池将用于进入该状态。注意:不需要对线程池的状态做特殊处理。线程池的状态是由线程池自己根据方法来定义和处理的。.4、合理分配线程的一些建议(1)对于CPU密集型任务,需要尽可能地压榨CPU。参考值可以设置为NCPU+1(CPU个数加1)。(2)对于IO密集型任务,参考值可以设置为2*NCPU(CPU个数乘以2)5、线程池的核心类之一——ThreadPoolExecutor1。构造方法ThreadPoolExecutor参数最多的构造方法如下:publicThreadPoolExecutor(intcorePoolSize,intmaximumPoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerrejectHandler)其他构造方法调用该构造方法实例化对象。可以说我们直接分析了这个方法之后,也就明白了其他构造方法是怎么回事!下面对该施工方法进行详细分析。注:为了更深入的分析ThreadPoolExecutor类的构造方法,将适当调整参数的顺序进行分析,让大家更深入的了解ThreadPoolExecutor构造方法中各个参数的作用。上述构造方法接收以下参数进行初始化:(1)corePoolSize:核心线程数。(2)maximumPoolSize:最大线程数。(3)workQueue:阻塞队列,存放等待执行的任务,非常重要,会对线程池的运行过程产生重大影响。其中,以上三个参数之间的关系如下:如果运行的线程数小于corePoolSize,则直接创建一个新的线程来处理任务,即使线程池中的其他线程空闲。如果正在运行的线程数大于等于corePoolSize且小于maximumPoolSize,此时只有当workQueue满了才会创建新的线程来处理任务。如果设置的corePoolSize与maximumPoolSize相同,则创建的线程池大小是固定的。此时如果有新任务提交,workQueue未满,则将请求放入workQueue,等待空闲线程,从workQueue中取出任务进行处理。如果运行线程数大于maximumPoolSize且workQueue已满,则通过拒绝策略参数rejectHandler指定处理策略。根据以上三个参数的配置,线程池会对任务进行如下处理:当有新的任务提交给线程池时,线程池会根据当前运行的线程数来决定任务的处理方式线程池。处理方式有直接切换、使用无限队列、使用有界队列三种。直接切换常用的队列是SynchronousQueue。使用无限队列就是使用基于链表的队列,比如LinkedBlockingQueue。如果使用该方法,线程池中创建的线程最大数量为corePoolSize,此时maximumPoolSize不起作用。当线程池中的所有核心线程都在运行时,提交新的任务会将其放入等待队列。ArrayBlockingQueue用于有界队列。这样可以将线程池??的最大线程数限制为maximumPoolSize,可以减少资源消耗。但是,这种方式使得线程池调度线程变得更加困难,因为线程池和队列的容量是有限的。根据以上三个参数,我们可以简单的得出一些如何降低系统资源消耗的措施:如果要降低系统资源消耗,包括CPU占用率、操作系统资源消耗、上下文切换开销等,可以设置一个lower大队列容量和小线程池容量。这样会降低线程处理任务的吞吐量。如果提交的任务经常阻塞,可以考虑调用设置最大线程数的方法重新设置线程池的最大线程数。如果队列的容量设置的比较小,通常需要将线程池的容量设置的大一些,这样CPU占用率就会高一些。如果线程池的容量设置过大,会增加并发量,需要考虑线程调度,可能会降低处理任务的吞吐量。接下来我们继续看ThreadPoolExecutor的构造函数的参数。(4)keepAliveTime:当没有任务执行时,线程最多保持多久?当线程池中的线程数大于corePoolSize时,如果此时没有新的任务提交,则核心线程之外的线程不会立即销毁,需要等到wait时间超过keepAliveTime,才会被终止。(5)unit:keepAliveTime的时间单位(6)threadFactory:线程工厂,用于创建线程。默认情况下,提供了一个默认工厂来创建线程。使用默认工厂创建线程时,新创建的线程会拥有相同的Priority,并且是非守护线程,同时设置线程名(7)rejectHandler:拒绝处理任务时的策略如果workQueue阻塞队列已满,没有空闲的线程池,此时继续提交任务,就需要采用一种策略来处理这个任务。线程池一共提供了四种策略:直接抛出异常也是默认的策略。实现类是AbortPolicy。在与调用者相同的线程上执行任务。实现类是CallerRunsPolicy。丢弃队列中最前面的任务并执行当前任务。实现类是DiscardOldestPolicy。直接丢弃当前任务。实现类是DiscardPolicy。2、ThreadPoolExecutor提供的启动和停止任务的方法(1)execute():将任务提交到线程池执行(2)submit():提交任务并返回执行结果execute+Future(3)shutdown():关闭线程池,等待任务执行完(4)shutdownNow():立即关闭线程池,不等待任务执行完任务总数(2)getCompletedTaskCount():任务数ofcompletedtasks(3)getPoolSize():当前线程池线程数(4)getCorePoolSize():线程池核心线程数(5)getActiveCount():当前线程池线程数正在执行任务的还是不错的,今天就到这里吧,我是冰河,下次见~~