在Java面试中,线程池相关知识,虽不能说是必问提,但出现的频次也是非常高的。同时又鉴于公众号“程序新视界”的读者后台留言让写一篇关于Java线程池的文章,于是就有本篇内容,本篇将基于Java线程池的原理、实现以及相关源码进行讲解等。

什么是线程池

线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

为了充分利用CPU多核资源,应用都会采用多线程并行/并发计算,最大限度的利用多核提升应用程序性能。

试想一下,如果每个请求都执行一遍创建线程、执行任务、销毁线程,那么对服务器资源将是一种浪费。在高并发的情况下,甚至会耗尽服务器资源。

线程池的主要作用有两个:不同请求之间重复利用线程,无需频繁的创建和销毁线程,降低系统开销和控制线程数量上限,避免创建过多的线程耗尽进程内存空间,同时减少线程上下文切换次数。

常见面试题

  • 说说Java线程池的好处及实现的原理?
  • Java提供线程池各个参数的作用,如何进行的?
  • 根据线程池内部机制,当提交新任务时,有哪些异常要考虑?
  • 线程池都有哪几种工作队列?
  • 使用无界队列的线程池会导致内存飙升吗?
  • 说说几种常见的线程池及使用场景?

线程池的创建与使用

在JDK5版本中增加了内置线程池实现ThreadPoolExecutor,同时提供了Executors来创建不同类型的线程池。Executors中提供了以下常见的线程池创建方法:

  • newSingleThreadExecutor:一个单线程的线程池。如果因异常结束,会再创建一个新的,保证按照提交顺序执行。
  • newFixedThreadPool:创建固定大小的线程池。根据提交的任务逐个增加线程,直到最大值保持不变。如果因异常结束,会新创建一个线程补充。
  • newCachedThreadPool:创建一个可缓存的线程池。会根据任务自动新增或回收线程。
  • newScheduledThreadPool:支持定时以及周期性执行任务的需求。
  • newWorkStealingPool:JDK8新增,根据所需的并行层次来动态创建和关闭线程,通过使用多个队列减少竞争,底层使用ForkJoinPool来实现。优势在于可以充分利用多CPU,把一个任务拆分成多个“小任务”,放到多个处理器核心上并行执行;当多个“小任务”执行完成之后,再将这些执行结果合并起来即可。

虽然在JDK中提供Executors类来支持以上类型的线程池创建,但通常情况下不建议开发人员直接使用(见《阿里巴巴java开发规范》)。

线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

Executors部分方法的弊端:

  • newFixedThreadPool和newSingleThreadExecutor主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
  • newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

同时,阿里巴巴java开发规范中推荐了3种线程池创建方式。

方式一,引入commons-lang3包。

//org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,     new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());

方式二,引入com.google.guava包。

ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()     .setNameFormat("demo-pool-%d").build();  //Common Thread Pool ExecutorService pool = new ThreadPoolExecutor(5, 200,     0L, TimeUnit.MILLISECONDS,     new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());  pool.execute(()-> System.out.println(Thread.currentThread().getName())); pool.shutdown();//gracefully shutdown

方式三,spring配置线程池方式:自定义线程工厂bean需要实现ThreadFactory,可参考该接口的其它默认实现类,使用方式直接注入bean,调用execute(Runnable task)方法即可。

<bean id="userThreadPool"     class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">     <property name="corePoolSize" value="10" />     <property name="maxPoolSize" value="100" />     <property name="queueCapacity" value="2000" />  <property name="threadFactory" value= threadFactory />     <property name="rejectedExecutionHandler">         <ref local="rejectedExecutionHandler" />     </property> </bean> // in code userThreadPool.execute(thread);

ThreadPoolExecutor的构造方法

除了以上推荐的创建线程池的方法,还可以通过ThreadPoolExecutor的构造方法,直接创建线程池。本质上来讲,以上方法最终也是创建了ThreadPoolExecutor对象,然后堆积进行包装处理。

ThreadPoolExecutor提供了多个构造方法,我们最终都调用的构造方法来进行说明。

 public ThreadPoolExecutor(int corePoolSize,       int maximumPoolSize,       long keepAliveTime,       TimeUnit unit,       BlockingQueue<Runnable> workQueue,       ThreadFactory threadFactory,       RejectedExecutionHandler handler) {    // 省略代码 }

核心参数作用解析如下:

  • corePoolSize:线程池核心线程数最大值。
  • maximumPoolSize:线程池最大线程数大小。
  • keepAliveTime:线程池中非核