ThreadPoolExecutor 线程池理论、饱和策略、工作队列排队策略

目录

ThreadPoolExecutor 线程池概述

Executor 框架结构

线程池饱和策略

线程池工作流程图

工作队列排队策略


ThreadPoolExecutor 线程池概述

1、线程池是JDK1.5开始引入的,也叫Executor框架,或是Java并发框架

2、线程池相关的API在java.util.concurrent包中,常用到以下几个类和接口:

2.1、java.util.concurrent.Executor:一个只包含一个方法的接口,它的抽象含义是:用来执行一个Runnable任务的执行器

2.2、java.util.concurrent.ExecutorService:继承了Executor接口的接口,增加了很多对于任务和执行器的生命周期进行管理的方法

2.3、java.util.concurrent.ThreadFactory:一个生成新线程的接口。用户可以通过实现这个接口管理对线程池中生成线程的逻辑

2.4、java.util.concurrent.Executors:创建并返回其余各个实例的类,提供了很多不同的生成执行器的实用方法,比如基于线程池的执行器的实现。

2.5、java.util.concurrent.ThreadPoolExecutor:这个类维护了一个线程池,对于提交到此Executor中的任务,它不是创建新的线程而是使用池内的线程进行执行,对于数量巨大但执行时间很短的任务,可以显著地减少对于任务执行的开销。

Executor 框架结构

1、executor 结构主要包括任务、任务的执行和异步结果的计算。

2、任务:包括被执行任务需要实现的接口,如Runnable接口或Callable接口

3、任务的执行:包括任务执行机制的核心接口Executor,以及继承自Executor的ExecutorService接口。Executor框架有两个关键类实现了ExecutorService接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)

4、异步计算的结果:包括接口Future和实现Future接口的FutureTask类

使用线程池的好处:

1、降低资源消耗:可以重复利用已创建的线程降低线程创建和销毁造成的消耗。

2、提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行。

3、提高线程的可管理性:线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控

线程池工作原理

1、当一个新的任务提交到线程池之后,线程池处理过程如下:

1.1、线程池判断核心线程池里的线程是否已满。未满时,则创建一个新的工作线程来执行任务。如果核心线程池里的线程已满,则执行第二步。

1.2、线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里等待执行。如果工作队列满了,则执行第三步。

1.3、线程池判断线程池(核心线程池外的线程池部分)的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

线程池饱和策略

1、常用的饱和策略如下(它们是 ThreadPoolExecutor 类中的内部类,可以直接调用):

Modifier and TypeClass and Description
static class ThreadPoolExecutor.AbortPolicy被拒绝的任务的处理程序,抛出一个 RejectedExecutionException
static class ThreadPoolExecutor.CallerRunsPolicy

一个被拒绝的任务的处理程序,直接在 execute方法的调用线程中运行被拒绝的任务,除非执行程序已经被关闭,否则这个任务被丢弃。

static class
ThreadPoolExecutor.DiscardOldestPolicy

被拒绝的任务的处理程序,丢弃最旧的未处理请求,然后重试 execute ,除非执行程序关闭,在这种情况下,任务被丢弃。

static class ThreadPoolExecutor.DiscardPolicy

被拒绝的任务的处理程序静默地丢弃被拒绝的任务。

AbortPolicy:Java 线程池默认的阻塞策略,即不执行此新任务,而且直接抛出一个运行时异常,切记ThreadPoolExecutor.execute 需要 try catch,否则程序会直接退出。

DiscardPolicy:直接抛弃,新任务不执行,空方法

DiscardOldestPolicy:从队列里面抛弃head的一个任务,并再次execute 此task。

用户自定义拒绝策略(最常用):实现RejectedExecutionHandler,并自己定义策略模式

线程池工作流程图

1、以 ThreadPoolExecutor 为例展示线程池的工作流程

1、如果当前运行的线程少于corePoolSize(核心线程数),则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。

2、如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue(阻塞队列/任务队列)。

3、如果无法将任务加入BlockingQueue(队列已满),则在非corePool中创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。

4、如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并执行线程饱和策略,如:RejectedExecutionHandler.rejectedExecution()方法。

5、ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

工作队列排队策略

1、已经说过当线程池中工作线程的总数量超过核心线程数量后,新加的任务就会放入工作队列中进行等待被执行

2、使用线程池就得创建ThreadPoolExecutor对象,通过ThreadPoolExecutor(线程池)类的构造方法创建时,就得指定工作队列,它是BlockingQueue<Runnable>接口,而实际开发中是指定此接口的具体实现类,常用的如下所示。

SynchronousQueue 直接提交

1、直接提交策略----意思是工作队列不保存任何任务被等待执行,而是直接提交给线程进行执行。

2、工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保存它们。

3、如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。

4、此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。

5、Executors的newCacheThreadPool()方法创建线程池,就是使用的此种排队策略

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

 LinkedBlockingQueue 无界队列

1、无界队列策略----无界指的是工作队列大小没有上限,可以添加无数个任务进行等待。

2、使用无界队列将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。于是创建的线程就不会超过 corePoolSize。因此,maximumPoolSize 的值也就无效了。所以一般让corePoolSize等于maximumPoolSize

3、当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列 

4、Executors的newFixedThreadPool(int nThreads)方法创建线程池,就是使用的此种排队策略

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

ArrayBlockingQueue 有界队列

1、有界队列策略----意思是工作队列的大小是有限制的

2、优点是可以防止资源耗尽的情况发生,因为如果工作队列被无休止的添加任务也是很危险的

3、当工作队列排满后,就会执行线程饱和策略

// 构造线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 4, 
        3, TimeUnit.SECONDS, 
        new ArrayBlockingQueue<Runnable>(2),
        new ThreadPoolExecutor.DiscardOldestPolicy());

4、如上核心线程为3个,每个线程的工作队列大小为2(即队列中最多有两个任务在等待执行),线程池最大线程数为4个

5、所以当工作线程数小于等于3时,直接新建线程执行任务;超过3时,任务会被添加进工作队列进行等待,3*2=6,当工作队列等待的任务数超过6个以后,则又会新建一个线程,此时整个线程池线程总数已经达到了4个,当还有任务进行添加时,此时将采取饱和策略。

ThreadPoolExecutor线程池的阻塞队列大小决定了可以容纳等待执行的任务数量。在ThreadPoolExecutor中,可以使用不同的阻塞队列实现来控制任务的排队和执行。 常用的阻塞队列实现有以下几种: 1. 直接提交队列(SynchronousQueue):该队列不存储任务,而是直接将任务提交给线程池中的工作线程进行执行,如果没有可用的工作线程,则尝试创建一个新线程来执行任务。这种情况下,设置队列大小为0或者1没有意义。 2. 有界队列(ArrayBlockingQueue):该队列有固定的容量,可以在创建ThreadPoolExecutor时指定队列的大小。当线程池中的线程数达到最大线程数时,后续的任务会被放入到该队列中等待执行。 3. 无界队列(LinkedBlockingQueue):该队列没有固定的容量,可以根据需要动态地增加其大小。当线程池中的线程数达到最大线程数时,后续的任务会被放入到该队列中等待执行。因为队列没有大小限制,所以可能会导致内存溢出。 4. 优先级队列(PriorityBlockingQueue):该队列根据任务的优先级进行排序,具有更高优先级的任务会被优先执行。可以在创建ThreadPoolExecutor时指定比较器来定义任务的优先级。 需要根据具体的场景和需求选择合适的阻塞队列实现和大小设置。如果任务量较大,可以选择有界队列或者优先级队列,以控制线程池的负载。如果任务量不确定或者需要动态增加队列的大小,可以选择无界队列
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蚩尤后裔-汪茂雄

芝兰生于深林,不以无人而不芳。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值