感谢牛客网网友提供的面试经验!
参考链接:https://www.jianshu.com/p/50fffbf21b39
1. 什么是线程池?
线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程(提高线程复用,减少性能开销)。java.util.concurrent.ThreadPoolExecutor类就是一个线程池。
2. 线程池有什么优势?
- 线程池可以重复利用已创建的线程,一次创建可以执行多次任务,有效降低线程创建和销毁所造成的资源消耗;
- 由于线程不用每次使用前都被创建。线程池技术使得请求可以快速得到响应,节约了创建线程的时间;
- 线程池可以对线程进行管理。线程的创建需要占用系统内存,消耗系统资源,使用线程池可以更好的管理线程,做到统一分配、调优和监控线程,提高系统的稳定性。
3. 线程池的主要参数有什么?
- int corePoolSize (core:核心的) = > 该线程池中核心线程数最大值
什么是核心线程:线程池新建线程的时候,如果当前线程总数小于 corePoolSize ,则新建的是核心线程;如果超过corePoolSize,则新建的是非核心线程。 - int maximumPoolSize = > 该线程池中线程总数的最大值
线程总数计算公式 = 核心线程数 + 非核心线程数。 - long keepAliveTime = > 该线程池中非核心线程闲置超时时长
核心线程默认情况下会一直存活在线程池中,即使这个核心线程啥也不干(闲置状态)。如果指定ThreadPoolExecutor的 allowCoreThreadTimeOut 这个属性为true,那么核心线程如果不干活(闲置状态)的话,超过一定时间(keepAliveTime),就会被销毁掉。 - TimeUnit unit = > (时间单位)
首先,TimeUnit是一个枚举类型,翻译过来就是时间单位,我们最常用的时间单位包括:MILLISECONDS : 1毫秒 、SECONDS : 秒、MINUTES : 分、HOURS : 小时、DAYS : 天 - BlockingQueue workQueue = >( Blocking:阻塞的,queue:队列)
该线程池中的任务队列:维护着等待执行的Runnable对象。当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务, 可以选择以下几个阻塞队列见问题5。 - ThreadFactory threadFactory = > 创建线程的方式,这是一个接口
new它的时候需要实现他的Thread newThread(Runnable r)方法。 - RejectedExecutionHandler handler = > 这个主要是用来抛异常的
当线程无法执行新任务时(一般是由于线程池中的线程数量已经达到最大数或者线程池关闭导致的),默认情况下,当线程池无法处理新线程时,会抛出一个RejectedExecutionException。
4. 线程池的排队策略
当我们向线程池提交任务的时候,需要遵循一定的排队策略,分两种情况:
- 如果运行的线程少于corePoolSize,则Executor(执行者)始终首选添加新的线程,而不进行排队;
- 如果运行的线程等于或者多于corePoolSize,则Executor始终首选将请求加入队列,而不是添加新线程如果无法将请求加入队列,即队列已经满了,则创建新的线程,除非创建此线程超出maxinumPoolSize,在这种情况下,任务默认将被拒绝。
5. 线程池的四种队列类型
一般来说,workQueue有以下四种队列类型:
- SynchronousQueue:(同步队列)这个队列接收到任务的时候,会直接提交给线程处理,而不保留它(名字定义为 同步队列)。
但有一种情况,假设所有线程都在工作怎么办?
这种情况下,SynchronousQueue就会新建一个线程来处理这个任务。所以为了保证不出现(线程数达到了maximumPoolSize而不能新建线程)的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大,去规避这个使用风险。
-
LinkedBlockingQueue(链表阻塞队列):这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
-
ArrayBlockingQueue(数组阻塞队列):可以限定队列的长度(既然是数组,那么就限定了大小),接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
-
DelayQueue(延迟队列):队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
6. Java常见线程池有哪些?如何创建线程池?
通过 ThreadPoolExecutor 的构造方法实现:
- newCachedThreadPool:用来创建一个可以无限扩大的线程池,适用于负载较轻的场景,执行短期异步任务。(可以使得任务快速得到执行,因为任务时间执行短,可以很快结束,也不会造成cpu过度切换)
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool:创建一个固定大小的线程池,因为采用无界的阻塞队列,所以实际线程数量永远不会变化,适用于负载较重的场景,对当前线程数量进行限制。(保证线程数可控,不会造成线程过多,导致系统负载更为严重)
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newSingleThreadExecutor:创建一个单线程的线程池,适用于需要保证顺序执行各个任务。
创建一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
- newScheduledThreadPool:适用于执行延时或者周期性任务。
创建一个定长任务线程池,支持定时及周期性任务执行。