Java后端高频面试问题:线程池
1.什么是线程池?
2.使用线程池的好处?
①降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
②提高响应速度:当任务到达时,可以不需要等待线程创建就能立即执行。
3.线程池的工作流程?
①判断核心线程数是否已满,如果没满,及时现在有空闲线程,也创建一个新的工作线程来执行任务
②如果核心线程数已满,再判断任务队列是否已满,如果没满则将新提交的任务添加在工作队列中。
4.怎么创建一个线程池?
1.通过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; }
2.通过Executors工具类创建。
Java 5新增了一个Executors工厂类来产生线程池,该工厂类包含
如下几个静态工厂方法来创建线程池。创建出来的线程池,都是通过ThreadPoolExecutor类来实现的。
5.重复创建线程为什么会开销过大?
Java线程的创建成本很高,因为需要进行大量的工作:
1.必须为线程堆栈分配和初始化大量内存块。
2.需要进行系统调用,以便在主机OS中创建/注册本机线程。
6.线程池的种类?
①newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程
将会被缓存在线程池中。
②newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。
③newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于调用newFixedThread
Pool()方法时传入参数为1。
④newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延
迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池
内。
⑤newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行
线程任务。
⑥ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持
给定的并行级别,该方法还会使用多个队列来减少竞争。
⑦ExecutorService newWorkStealingPool():该方法是前一个方法的简化版本。如果当前机器有4个
CPU,则目标并行级别被设置为4,也就是相当于为前一个方法传入4作为参数。
7.线程池有哪些状态?
①Running:能接受新提交的任务,能处理阻塞队列中的任务。
②Shutdown:不接受新提交的任务,可以处理阻塞队列中已有的任务。
③Stop:不接受新任务,也不处理队列中的任务,中断正在处理任务的线程。
④Tiding:所有任务都执行完(包含阻塞队列里面的任务),当前线程池活动线程数workerCount为0,
线程池进入该状态后会调用terminate()方法进入Terminated状态。
8.线程池有哪些参数?每个参数的作用是什么?
①corePoolSize(核心工作线程数):当向线程池提交一个任务时,若线程池已创建的线程数小于
corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线
程数大于或等于corePoolSize时。
②maximumPoolSize(最大线程数):线程池所允许的最大线程个数。当队列满了,且已创建的线
程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽
略该参数。
③keepAliveTime(多余线程存活时间):当线程池中线程数大于核心线程数时,线程的空闲时间如
果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。
④TimeUnit(时间单位):keepAliveTime的时间单位。
⑤workQueue(队列):用于传输和保存等待执行任务的阻塞队列。
⑥threadFactory(线程创建工厂):用于创建新线程。threadFactory创建的线程也是采用newThread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。
⑦handler(拒绝策略):当线程池和队列都满了,再加入线程会执行此策略。
9.线程池的拒绝策略?
当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就
会采取任务拒绝策略,通常有以下四种策略:
1. AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
2. DiscardPolicy:也是丢弃任务,但是不抛出异常。
3. DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复该过程)。
4. CallerRunsPolicy:由调用线程处理该任务。
10.线程池的阻塞队列?
阻塞队列
①第一种阻塞队列是 LinkedBlockingQueue。对应的线程池:newSingleThreadExecutor( )和newFixedThreadPool(int n)
LinkedBlockingQueue,它的容量是 Integer.MAX_VALUE,为 231 -1 ,是一个非常大的值,可以认为是无界队列。
FixedThreadPool 和 SingleThreadExecutor 线程池的线程数是固定的,所以没有办法增加特别多的线程来处理任务,这时就需要 LinkedBlockingQueue 这样一个没有容量限制的阻塞队列来存放任务。
②第二种阻塞队列是 SynchronousQueue。对应的线程池是 newCachedThreadPool( )
线程池 CachedThreadPool 的最大线程数是 Integer.MAX_VALUE,可以理解为线程数是可以无限扩展的。
CachedThreadPool 和上一种线程池 FixedThreadPool 的情况恰恰相反,FixedThreadPool 的情况是阻塞队列的容量是无限的,而这里 CachedThreadPool 是线程数可以无限扩展,所以 CachedThreadPool 线程池并不需要一个任务队列来存储任务,因为一旦有任务被提交就直接转发给线程或者创建新线程来执行,而不需要另外保存它们。
我们自己创建使用 SynchronousQueue 的线程池时,如果不希望任务被拒绝,那么就需要注意设置最大线程数要尽可能大一些,以免发生任务数大于最大线程数时,没办法把任务放到队列中也没有足够线程来执行任务的情况。
③第三种阻塞队列是DelayedWorkQueue。对应的线程池分别是 newScheduledThreadPool (int n)和newSingleThreadScheduledExecutor( )
这两种线程池的最大特点就是可以延迟执行任务,比如说一定时间后执行任务或是每隔一定的时间执行一次任务。
DelayedWorkQueue 的特点是内部元素并不是按照放入的时间排序,而是会按照延迟的时间长短对任务进行排序,内部采用的是“堆”的数据结构(堆的应用之一就是 优先级队列)。之所以线程池 ScheduledThreadPool 和 SingleThreadScheduledExecutor 选择 DelayedWorkQueue,是因为它们本身正是基于时间执行任务的,而延迟队列正好可以把任务按时间进行排序,方便任务的执行。