线程池在JAVA知识中,占有相当重要的地位!下面看看面试官都会问线程池哪些方面的知识点,来考查我们对线程池基本知识撑握情况、及使用熟练度!
开始
面试官:用过JAVA中的线程池嘛?是怎么用的?能大概说下使用方法嘛?
我:在java.util.concurrent包下有个 ThreadPoolExecutor 工具类,可以用它创建一个普通的线程池。使用方法是:
@Bean
public ExecutorService executorService() {
return new ThreadPoolExecutor(
5,
150,
1500,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
new WorkThreadFactory("EXE")
);
}
如果要创建定时任务线程池,java.util.concurrent包下的 Executors 工具包创建。使用方法是,调用其中的方法:
@Bean
public ScheduledExecutorService scheduledExecutorService() {
return Executors.newScheduledThreadPool(
20,
new WorkThreadFactory("SCH")
);
}
面试官在问这个问题除了考查我们会不会用外,也在考查我们会不会去查看JDK下包的源码,会不会深入研究。
面试官:能详细讲下线程池的状态吗?
我:在 ThreadPoolExecutor 类中,定义了 线程池5各状态:
-
RUNNING 线程池可以接收新的任务和执行已添加的任务
-
SHUTDOWN 线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务
-
STOP 不接收新任务,不处理已添加的任务,并且会中断正在执行的任务
-
TIDYING 当所有的任务已终止,记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()
-
TERMINATED 当钩子函数terminated()被执行完成之后,线程池彻底终止,就变成TERMINATED状态
线程池中状态之间的转换:
当线程池被创建成功后,就处于 RUNNING 状态,这个时候线程池中的任务数为0,能够接收新任务,对已添加的任务进行处理。
当一个线程池调用 shutdown() 方法后,线程池从RUNNING -> SHUTDOWN
当调用线程池的shutdownNow()方法后,线程池由(RUNNING或者SHUTDOWN ) -> STOP
当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。当线程池在STOP状态下,线程池中执行的任务为空时,会由STOP -> TIDYING
线程池处在TIDYING状态时,执行完terminated()之后,就会由 TIDYING -> TERMINATED
面试官:常用的有哪几种线程池?
我:常用的有4种线程池
-
newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
创建一个固定大小的线程池,每次提交一个任务就创建一个线程,直到线程达到线程池的最大值nThreads
。线程池的大小一旦达到最大值后,再有新的任务提交时则放入无界阻塞队列中,等到有线程空闲时,再从队列中取出任务继续执行
-
newCachedThreadPool
-
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
创建了一个可缓存的线程池。当有新的任务提交时,有空闲线程则直接处理任务,没有空闲线程则创建新的线程处理任务,队列中不储存任务。线程池不对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。如果线程空闲时间超过了60秒就会被回收
-
newSingleThreadExecutor
创建了一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行
-
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
-
newScheduledThreadPool
创建了一个固定大小的线程池,支持定时及周期性任务执行
-
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
以上为四种常见的线程池,在理解线程池的时候,里面的参数也很重要。下面为最后调用的方法。其中有7个参数。
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;
}
corePoolSize : 为核心线程数量
maximumPoolSize: 线程池允许最大线程数
keepAliveTime : 表示能够呆多长时间,参数为数字
TimeUnit :为呆的时间单位。 秒;分
BlockingQueue : 为达到了核心线程池后,把任务添加到队列中
ThreadFactory :为线程工厂,就是用来创建线程
RjectedExecutionHandler : 为拒绝策略,在达到了最大线程数,同时队列也最大了
BlockingQueue 本身是一个接口,其功能可以使用默认的,也可以使用自已写的。可以 链表、数组等数组结构存储
RjectedExecutionHandler 拒绝策略有4种:
-
直接抛异常 能够知道线程已经超过最大限制了
-
直接丢弃,没提示 直接把任务丢掉
-
加入队列,替换队列中停留时间最长的
-
由提交线程处理
以上的4种策略,前3种会有数据丢失的现象。
面试官问这些问题,不论是参数、线程队列,还是拒绝策略,是对我们对线程池的知识进行了一个全面、低层的盘问。