微信公众号「后端进阶」,专一后端技术分享:Java、Golang、WEB框架、分布式中间件、服务治理等等。
老司机倾囊相授,带你一路进阶,来不及解释了快上车!java
多线程能够说是面试官最喜欢拿来问的题目之一了,可谓是老生之常谈,无论你是新手仍是老司机,我相信你必定会在面试过程当中遇到过有关多线程的一些问题。那我如今就充当一次面试官,我来问你:面试
现有一个线程池,参数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<Runnable> 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个参数的用途:this
线程池核心线程数量,核心线程不会被回收,即便没有任务执行,也会保持空闲状态。
池容许最大的线程数,当线程数量达到corePoolSize,且workQueue队列塞满任务了以后,继续建立线程。
超过corePoolSize以后的“临时线程”的存活时间。
keepAliveTime的单位。
当前线程数超过corePoolSize时,新的任务会处在等待状态,并存在workQueue中,BlockingQueue是一个先进先出的阻塞式队列实现,底层实现会涉及Java并发的AQS机制,有关于AQS的相关知识,我会单独写一篇,敬请期待。
建立线程的工厂类,一般咱们会自顶一个threadFactory设置线程的名称,这样咱们就能够知道线程是由哪一个工厂类建立的,能够快速定位。
线程池执行拒绝策略,当线数量达到maximumPoolSize大小,而且workQueue也已经塞满了任务的状况下,线程池会调用handler拒绝策略来处理请求。
系统默认的拒绝策略有如下几种:
咱们还能够自定义拒绝策略,只须要实现RejectedExecutionHandler接口便可,友好的拒绝策略实现有以下:
如今咱们回到刚开始的问题就很好回答了:
线程池corePoolSize=5,线程初始化时不会自动建立线程,因此当有4个任务同时进来时,执行execute方法会新建【4】条线程来执行任务;
前面的4个任务都没完成,如今又进来2个队列,会新建【1】条线程来执行任务,这时poolSize=corePoolSize,还剩下1个任务,线程池会将剩下这个任务塞进阻塞队列中,等待空闲线程执行;
若是前面6个任务仍是没有处理完,这时又同时进来了5个任务,此时尚未空闲线程来执行新来的任务,因此线程池继续将这5个任务塞进阻塞队列,但发现阻塞队列已经满了,核心线程也用完了,还剩下1个任务不知道如何是好,因而线程池只能建立【1】条“临时”线程来执行这个任务了;
这里建立的线程用“临时”来描述仍是由于它们不会长期存在于线程池,它们的存活时间为keepAliveTime,此后线程池会维持最少corePoolSize数量的线程。
JDK为咱们提供了Executors线程池工具类,里面有默认的线程池建立策略,大概有如下几种:
用Executors工具类虽然很方便,我依然不推荐你们使用以上默认的线程池建立策略,阿里巴巴开发手册也是强制不容许使用Executors来建立线程池,咱们从JDK源码中寻找一波答案:
java.util.concurrent.Executors:
// FixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// SingleThreadPool
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
// CachedThreadPool
public static ExecutorService newCachedThreadPool() {
// 容许建立线程数为Integer.MAX_VALUE
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
// 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异常。