当前位置: 首页 > 科技观察

Java并发线程池

时间:2023-03-12 19:26:17 科技观察

本文转载自微信公众号《怀梦追码》,作者水木盏。转载本文请联系怀梦追码公众号。线程池的作用池化技术是一种很常见的计算机技术,主要用于多路复用和提高性能,如内存池、连接池、对象池等。线程池也不例外,其主要作用如下:提高性能:频繁创建和销毁线程会产生很大的系统开销,线程池中的线程复用可以大大减少这种不必要的开销。复用和管理:方便对池中的线程进行管理和复用,避免在生产环境中创建大量线程。解耦:只暴露提交任务的接口,线程池的创建和销毁与业务解耦。JDK在concurrency包中为我们定义了一套Executor框架,帮助开发者有效的控制线程。有基本的线程池类和线程池工厂,但最重要的是ThreaPoolExecutor,这也是面试中被问到最多的知识。观点。本文重点介绍ThreaPoolExecutor的原理。线程池参数说明ThreaPoolExecutor(intcorePoolSize,longkeepAliveTime,TimeUnitunit,BlockingQueueworkQueue,ThreadFactorythreadFactory,RejectedExecutionHandlerhandler))ThreaPoolExecutor参数含义如下corePoolSize:线程池中核心线程数。maximumPoolSize:线程池中的最大线程数。keepAliveTime:当线程池数量超过corePoolSize时,冗余空闲线程的存活时间。即超过coolPoolSize的空闲线程多久销毁。unit:keepAliveTime的单位,可以是小时、分钟、秒等各种值。workQueue:任务队列,存放已经提交但还未执行??的任务。threadFactory:线程工厂,用于创建线程,一般使用默认。handler:拒绝策略,当线程池无法处理任务时,如何拒绝任务。上述参数中,workQueue、threadFactory、handler比较复杂,需要单独介绍。下面主要介绍ThreadFactory和RejectedExecutionHandler1。线程工厂:ThreadFactory线程池中的线程是由TrheadFactory定义的线程工厂创建的。它是一个只有ThreadnewThread(Runnabler)方法来创建线程的接口。虽然在创建ThreadPoolExecutor时不需要指定该参数,但阿里编码协议推荐指定该参数,其优点如下:跟踪线程池何时创建线程以及创建了多少线程。您可以自定义线程池的名称、组和优先级。设置线程的其他状态等,比如daemonizing。2、拒绝策略:RejectedExecutionHandler当线程池中的线程数达到maxPoolSize时,会提交一个新的任务来执行拒绝策略。JDK定义了四种拒绝策略:AbortPolicy这个策略直接抛出异常CallerRunsPolicy调用者线程处理任务,这个策略不是真正的丢弃任务,但是当前线程会执行丢弃的任务。由于只有一个线程,所以所有的任务都会串行执行。DiscardOldestPolicy丢弃最旧的请求,即排在队首的待执行任务,并尝试重新提交当前任务。DiscardPolicy该策略以静默方式丢弃无法处理的任务。以上四种拒绝策略都继承了RejectedExecutionHandler接口,实现了该接口的rejectedExecution(Runnabler,ThreadPoolExecutorexecutor)方法。如果以上四种拒绝策略都不能满足你的需求,你可以自定义拒绝策略,继承RejectedExecutionHandler接口并实现该方法。线程池ThreaPoolExecutor的调度逻辑对提交的任务进行如下处理:1、提交任务时:如果线程池中的线程数小于corePoolSize(无论是否有空闲线程),则创建一个新的线程(所谓的核心线程)来处理。如果线程池中的线程数大于或等于corePoolSize,则新提交的任务会被放入等待队列中进行调度。如果等待队列已满,线程池中的线程数小于maxPoolSize,则会继续创建新的线程来处理任务。如果队列满了,线程数已经达到上限,就会使用拒绝策略来处理。2、任务进行时:当队列中的任务执行完毕,部分线程开始空闲时,非核心线程在空闲后的keepAliveTime时间内自行销毁。空闲核心线程是否退出取决于线程池的另一个参数allowCoreThreadTimeOut。当配置为true时,即使是核心线程也会在超时时退出。线程池的生命周期线程池和线程一样有自己的生命周期,包括RUNNING、SHUTDOWN、STOP、TIDYING和TERMINATED五种状态。它们的转换关系如下图所示,这些转换是不可逆的。1.RUNNING这个状态是线程池的工作状态,可以接受新的任务,处理接受的任务。线程池的初始状态,即线程创建成功后处理该状态。2、在SHUTDOWN状态下,线程池不再接受新的任务,但可以继续处理提交给线程池的任务。当线程状态为RUNNING时,调用shutdown()方法进入该状态。3、在STOP状态下,线程池不接受新的任务,不处理阻塞队列中的任务,同时中断正在执行任务的线程。当线程处于RUNNING或SHUTDOWN状态时调用shutdownNow()方法进入该状态。4、TIDYING的所有任务都销毁,workCount为0,会自动从RUNNING或STOP状态转为TIDYING状态。terminated()方法将在转换过程中被调用。ThreadPoolExecutor类的ternimated()方法为空。如果想在线程池变为TIDYING时进行处理,可以重载该方法。当线程池处于SHUTDOWN状态,当阻塞??队列为空且执行任务为空时,会转为TIDYING状态;如果线程池处于STOP状态,当执行的任务为空时,它会转换到TIDYING状态。5.TERMINATED结束状态,线程池的最终状态,处于该状态的线程池不会有任何操作。执行完terminated()方法后线程池就处于这个状态。JDK的四种线程池了解了ThreaPoolExecutor的基本原理后,我们来看看JDK在Executors中为开发者定义的四种线程池工厂方法。实际上,它们在内部调用了ThreaPoolExecutor,只是使用了不同的参数。让我们了解一下他们的特点。newFixedThreadPool()方法:该方法返回线程池中固定数量的线程。提交任务时,如果线程池中有空闲线程,则立即执行。如果没有,新任务将缓存在任务队列中。它创建了一个线程池代码如下:publicstaticExecutorServicenewFixedThreadPool(intnthread){returnnewThreadPoolExecutor(nthread,nthread,0L,TimeUnit.MILLSECCONDS,newLikedBlockingQueue())}newSingleThreadExecutor()方法:该方法返回一个只有一个线程池线程,如果有冗余任务提交到线程池,则提交到任务队列。它创建线程池的代码如下:根据实际情况调整线程池数为0,线程总数为Integer.MAX_VALUE,队列使用SynchronousQueue,所以即使线程满了也无法向队列提交任务。publicstaticExecutorServicenewCachedThreadPool(){returnnewThreadPoolExecutor(0,Integer.MAX_VALUE,60L,TimeUnit.SECONDS,newSynchronousQueue());}newScheduledThreadPool():该方法为定长线程池,以延时或定时的方式执行任务.它的队列使用DelayedWorkQueue,所以任务必须继承Delay接口。publicstaticScheduledExecutorServicenewScheduledThreadPool(intcorePoolSize){returnnewScheduledThreadPoolExecutor(corePoolSize);}publicScheduledThreadPoolExecutor(intcorePoolSize){super(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,newDelayedWorkQueue());}