资讯专栏INFORMATION COLUMN

你都理解创建线程池的参数吗?

canopus4u / 3006人阅读

摘要:超过之后的临时线程的存活时间。为什么不建议使用创建线程池为我们提供了线程池工具类,里面有默认的线程池创建策略,大概有以下几种线程池线程数量固定,即和数量一样。创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于。

微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。  
老司机倾囊相授,带你一路进阶,来不及解释了快上车!

多线程可以说是面试官最喜欢拿来问的题目之一了,可谓是老生之常谈,不管你是新手还是老司机,我相信你一定会在面试过程中遇到过有关多线程的一些问题。那我现在就充当一次面试官,我来问你:

现有一个线程池,参数corePoolSize = 5,maximumPoolSize = 10,BlockingQueue阻塞队列长度为5,此时有4个任务同时进来,问:线程池会创建几条线程?

如果4个任务还没处理完,这时又同时进来2个任务,问:线程池又会创建几条线程还是不会创建?

如果前面6个任务还是没有处理完,这时又同时进来5个任务,问:线程池又会创建几条线程还是不会创建?

如果你此时一脸懵逼,请不要慌,问题不大。

创建线程池的构造方法的参数都有哪些?

要回答这个问题,我们需要从创建线程池的参数去找答案:

java.util.concurrent.ThreadPoolExecutor#ThreadPoolExecutor:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

创建线程池一共有7个参数,从源码可知,corePoolSize和maximumPoolSize都不能小于0,且核心线程数不能大于最大线程数。

下面我来解释一下这7个参数的用途:

corePoolSize

线程池核心线程数量,核心线程不会被回收,即使没有任务执行,也会保持空闲状态。如果线程池中的线程少于此数目,则在执行任务时创建。

maximumPoolSize

池允许最大的线程数,当线程数量达到corePoolSize,且workQueue队列塞满任务了之后,继续创建线程。

keepAliveTime

超过corePoolSize之后的“临时线程”的存活时间。

unit

keepAliveTime的单位。

workQueue

当前线程数超过corePoolSize时,新的任务会处在等待状态,并存在workQueue中,BlockingQueue是一个先进先出的阻塞式队列实现,底层实现会涉及Java并发的AQS机制,有关于AQS的相关知识,我会多带带写一篇,敬请期待。

threadFactory

创建线程的工厂类,通常我们会自顶一个threadFactory设置线程的名称,这样我们就可以知道线程是由哪个工厂类创建的,可以快速定位。

handler

线程池执行拒绝策略,当线数量达到maximumPoolSize大小,并且workQueue也已经塞满了任务的情况下,线程池会调用handler拒绝策略来处理请求。

系统默认的拒绝策略有以下几种:

AbortPolicy:为线程池默认的拒绝策略,该策略直接抛异常处理。

DiscardPolicy:直接抛弃不处理。

DiscardOldestPolicy:丢弃队列中最老的任务。

CallerRunsPolicy:将任务分配给当前执行execute方法线程来处理。

我们还可以自定义拒绝策略,只需要实现RejectedExecutionHandler接口即可,友好的拒绝策略实现有如下:

将数据保存到数据,待系统空闲时再进行处理

将数据用日志进行记录,后由人工处理

现在我们回到刚开始的问题就很好回答了:

线程池corePoolSize=5,线程初始化时不会自动创建线程,所以当有4个任务同时进来时,执行execute方法会新建【4】条线程来执行任务;

前面的4个任务都没完成,现在又进来2个队列,会新建【1】条线程来执行任务,这时poolSize=corePoolSize,还剩下1个任务,线程池会将剩下这个任务塞进阻塞队列中,等待空闲线程执行;

如果前面6个任务还是没有处理完,这时又同时进来了5个任务,此时还没有空闲线程来执行新来的任务,所以线程池继续将这5个任务塞进阻塞队列,但发现阻塞队列已经满了,核心线程也用完了,还剩下1个任务不知道如何是好,于是线程池只能创建【1】条“临时”线程来执行这个任务了;

这里创建的线程用“临时”来描述还是因为它们不会长期存在于线程池,它们的存活时间为keepAliveTime,此后线程池会维持最少corePoolSize数量的线程。

为什么不建议使用Executors创建线程池?

JDK为我们提供了Executors线程池工具类,里面有默认的线程池创建策略,大概有以下几种:

FixedThreadPool:线程池线程数量固定,即corePoolSize和maximumPoolSize数量一样。

SingleThreadPool:单个线程的线程池。

CachedThreadPool:初始核心线程数量为0,最大线程数量为Integer.MAX_VALUE,线程空闲时存活时间为60秒,并且它的阻塞队列为SynchronousQueue,它的初始长度为0,这会导致任务每次进来都会创建线程来执行,在线程空闲时,存活时间到了又会释放线程资源。

ScheduledThreadPool:创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。

用Executors工具类虽然很方便,我依然不推荐大家使用以上默认的线程池创建策略,阿里巴巴开发手册也是强制不允许使用Executors来创建线程池,我们从JDK源码中寻找一波答案:

java.util.concurrent.Executors:

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

// SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue()));
}

// CachedThreadPool
public static ExecutorService newCachedThreadPool() {
    // 允许创建线程数为Integer.MAX_VALUE
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue());
}

// ScheduledThreadPool
public ScheduledThreadPoolExecutor(int corePoolSize) {
    // 允许创建线程数为Integer.MAX_VALUE
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }
public LinkedBlockingQueue() {
    // 允许队列长度最大为Integer.MAX_VALUE
    this(Integer.MAX_VALUE);
}

从JDK源码可看出,Executors工具类无非是把一些特定参数进行了封装,并提供一些方法供我们调用而已,我们并不能灵活地填写参数,策略过于简单,不够友好

CachedThreadPool和ScheduledThreadPool最大线程数为Integer.MAX_VALUE,如果线程无限地创建,会造成OOM异常。

LinkedBlockingQueue基于链表的FIFO队列,是无界的,默认大小是Integer.MAX_VALUE,因此FixedThreadPool和SingleThreadPool的阻塞队列长度为Integer.MAX_VALUE,如果此时队列被无限地堆积任务,会造成OOM异常。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74804.html

相关文章

  • 关于线程池你不得不知道的一些设置

    摘要:有细心的网友早就想到了这个问题在线程池中,还有一些不常用的设置。所以该方法会在线程池总预先创建没有任务执行的线程,数量为。下面我们测试一下从测试结果来看,线程池中已经预先创建了数量的空闲线程。 微信公众号「后端进阶」,专注后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。 老司机倾囊相授,带你一路进阶,来不及解释了快上车! 看完我上一篇文章「你都理解创建线...

    余学文 评论0 收藏0
  • 技术经理:求求你,别再乱改数据库连接池的大小了!

    摘要:你仅仅需要一个大小为数据库连接池,然后让剩下的业务线程都在队列里等待就可以了。你应该经常会看到一些用户量不是很大的应用中,为应付大约十来个的并发,却将数据库连接池设置成,的情况。请不要过度配置您的数据库连接池的大小。 文章翻译整理自: https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing欢迎关注个人微信公众...

    darkbug 评论0 收藏0
  • 技术经理:求求你,别再乱改数据库连接池的大小了!

    摘要:你仅仅需要一个大小为数据库连接池,然后让剩下的业务线程都在队列里等待就可以了。你应该经常会看到一些用户量不是很大的应用中,为应付大约十来个的并发,却将数据库连接池设置成,的情况。请不要过度配置您的数据库连接池的大小。 文章翻译整理自: https://github.com/brettwooldridge/HikariCP/wiki/About-Pool-Sizing欢迎关注个人微信公众...

    苏丹 评论0 收藏0
  • 你真的懂ThreadPoolExecutor线程池技术?看了源码你会有全新的认识

    摘要:参数说明,线程池保留的最小线程数。,线程池中允许拥有的最大线程数。,线程池的运行状态。除非线程池状态发生了变化,发退回到外层循环重新执行,判断线程池的状态。是线程池的核心控制状态,包含的线程池运行状态和有效线程数。 Java是一门多线程的语言,基本上生产环境的Java项目都离不开多线程。而线程则是其中最重要的系统资源之一,如果这个资源利用得不好,很容易导致程序低效率,甚至是出问题。 有...

    JerryC 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<