各位志同道合的朋友们大家好,我是一个一直在一线互联网踩坑十余年的编码爱好者,现在将我们的各种经验以及架构实战分享出来,如果大家喜欢,就关注我,一起将技术学深学透,我会每一篇分享结束都会预告下一专题

线程池的创建分为两种方式:ThreadPoolExecutor 和 Executors,上一节学习了 ThreadPoolExecutor 的使用方式,本节重点来看 Executors 是如何创建线程池的。Executors 可以创建以下六种线程池。FixedThreadPool(n):创建一个数量固定的线程池,超出的任务会在队列中等待空闲的线程,可用于控制程序的最大并发数。

CachedThreadPool():短时间内处理大量工作的线程池,会根据任务数量产生对应的线程,并试图缓存线程以便重复使用,如果限制 60 秒没被使用,则会被移除缓存。

SingleThreadExecutor():创建一个单线程线程池。

ScheduledThreadPool(n):创建一个数量固定的线程池,支持执行定时性或周期性任务。

SingleThreadScheduledExecutor():此线程池就是单线程的 newScheduledThreadPool。

WorkStealingPool(n):Java 8 新增创建线程池的方法,创建时如果不设置任何参数,则以当前机器处理器个数作为线程个数,此线程池会并行处理任务,不能保证执行顺序。

下面分别来看以上六种线程池的具体代码使用。

FixedThreadPool 使用

创建固定个数的线程池,具体示例如下:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(2);

for (int i = 0; i < 3; i++) {

fixedThreadPool.execute(() -> {

System.out.println("CurrentTime - " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

以上程序执行结果如下:CurrentTime - 2019-06-27 20:58:58

CurrentTime - 2019-06-27 20:58:58

CurrentTime - 2019-06-27 20:58:59

根据执行结果可以看出,newFixedThreadPool(2) 确实是创建了两个线程,在执行了一轮(2 次)之后,停了一秒,有了空闲线程,才执行第三次。

CachedThreadPool 使用

根据实际需要自动创建带缓存功能的线程池,具体代码如下:

ExecutorService cachedThreadPool = Executors.newCachedThreadPool();

for (int i = 0; i < 10; i++) {

cachedThreadPool.execute(() -> {

System.out.println("CurrentTime - " +

LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

以上程序执行结果如下:CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

CurrentTime - 2019-06-27 21:24:46

根据执行结果可以看出,newCachedThreadPool 在短时间内会创建多个线程来处理对应的任务,并试图把它们进行缓存以便重复使用。

SingleThreadExecutor 使用

创建单个线程的线程池,具体代码如下:

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();

for (int i = 0; i < 3; i++) {

singleThreadExecutor.execute(() -> {

System.out.println("CurrentTime - " +

LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

以上程序执行结果如下:CurrentTime - 2019-06-27 21:43:34

CurrentTime - 2019-06-27 21:43:35

CurrentTime - 2019-06-27 21:43:36

ScheduledThreadPool 使用

创建一个可以执行周期性任务的线程池,具体代码如下:

ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);

scheduledThreadPool.schedule(() -> {

System.out.println("ThreadPool:" + LocalDateTime.now());

}, 1L, TimeUnit.SECONDS);

System.out.println("CurrentTime:" + LocalDateTime.now());

以上程序执行结果如下:CurrentTime:2019-06-27T21:54:21.881

ThreadPool:2019-06-27T21:54:22.845

根据执行结果可以看出,我们设置的 1 秒后执行的任务生效了。

SingleThreadScheduledExecutor 使用

创建一个可以执行周期性任务的单线程池,具体代码如下:

ScheduledExecutorService singleThreadScheduledExecutor = Executors.newSingleThreadScheduledExecutor();

singleThreadScheduledExecutor.schedule(() -> {

System.out.println("ThreadPool:" + LocalDateTime.now());

}, 1L, TimeUnit.SECONDS);

System.out.println("CurrentTime:" + LocalDateTime.now());

WorkStealingPool 使用

Java 8 新增的创建线程池的方式,可根据当前电脑 CPU 处理器数量生成相应个数的线程池,使用代码如下:

ExecutorService workStealingPool = Executors.newWorkStealingPool();

for (int i = 0; i < 5; i++) {

int finalNumber = i;

workStealingPool.execute(() -> {

System.out.println("I:" + finalNumber);

});

}

Thread.sleep(5000);

以上程序执行结果如下:I:0

I:3

I:2

I:1

I:4

根据执行结果可以看出,newWorkStealingPool 是并行处理任务的,并不能保证执行顺序。

ThreadPoolExecutor VS Executors

ThreadPoolExecutor 和 Executors 都是用来创建线程池的,其中 ThreadPoolExecutor 创建线程池的方式相对传统,而 Executors 提供了更多的线程池类型(6 种),但很不幸的消息是在实际开发中并不推荐使用 Executors 的方式来创建线程池。

无独有偶《阿里巴巴 Java 开发手册》中对于线程池的创建也是这样规定的,内容如下:线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的读者更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1)FixedThreadPool 和 SingleThreadPool:

允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2)CachedThreadPool 和 ScheduledThreadPool:

允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

OOM 是 OutOfMemoryError 的缩写,指内存溢出的意思。

为什么不允许使用 Executors?

我们先来看一个简单的例子:

ExecutorService maxFixedThreadPool = Executors.newFixedThreadPool(10);

for (int i = 0; i < Integer.MAX_VALUE; i++) {

maxFixedThreadPool.execute(()->{

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

});

}

之后设置 JVM(Java 虚拟机)的启动参数: -Xmx10m -Xms10m (设置 JVM 最大运行内存等于 10M)运行程序,会抛出 OOM 异常,信息如下:Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

at java.util.concurrent.LinkedBlockingQueue.offer(LinkedBlockingQueue.java:416)

at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1371)

at xxx.main(xxx.java:127)

为什么 Executors 会存在 OOM 的缺陷?

通过以上代码,找到了 FixedThreadPool 的源码,代码如下:

public static ExecutorService newFixedThreadPool(int nThreads) {

return new ThreadPoolExecutor(nThreads, nThreads,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue());

}

可以看到创建 FixedThreadPool 使用了 LinkedBlockingQueue 作为任务队列,继续查看 LinkedBlockingQueue 的源码就会发现问题的根源,源码如下:

public LinkedBlockingQueue() {

this(Integer.MAX_VALUE);

}

当使用 LinkedBlockingQueue 并没有给它指定长度的时候,默认长度为 Integer.MAX_VALUE,这样就会导致程序会给线程池队列添加超多个任务,因为任务量太大就有造成 OOM 的风险。

相关面试题

1.以下程序会输出什么结果?

public static void main(String[] args) {

ExecutorService workStealingPool = Executors.newWorkStealingPool();

for (int i = 0; i < 5; i++) {

int finalNumber = i;

workStealingPool.execute(() -> {

System.out.print(finalNumber);

});

}

}

A:不输出任何结果B:输出 0 到 9 有序数字C:输出 0 到 9 无需数字D:以上全对

答:A题目解析:newWorkStealingPool 内部实现是 ForkJoinPool,它会随着主程序的退出而退出,因为主程序没有任何休眠和等待操作,程序会一闪而过,不会执行任何信息,所以也就不会输出任何结果。

2.Executors 能创建单线程的线程池吗?怎么创建?

答:Executors 可以创建单线程线程池,创建分为两种方式:Executors.newSingleThreadExecutor():创建一个单线程线程池。

Executors.newSingleThreadScheduledExecutor():创建一个可以执行周期性任务的单线程池。

3.Executors 中哪个线程适合执行短时间内大量任务?

答:newCachedThreadPool() 适合处理大量短时间工作任务。它会试图缓存线程并重用,如果没有缓存任务就会新创建任务,如果线程的限制时间超过六十秒,则会被移除线程池,因此它比较适合短时间内处理大量任务。

4.可以执行周期性任务的线程池都有哪些?

答:可执行周期性任务的线程池有两个,分别是:newScheduledThreadPool() 和 newSingleThreadScheduledExecutor(),其中 newSingleThreadScheduledExecutor() 是 newScheduledThreadPool() 的单线程版本。

5.JDK 8 新增了什么线程池?有什么特点?

答:JDK 8 新增的线程池是 newWorkStealingPool(n),如果不指定并发数(也就是不指定 n),newWorkStealingPool() 会根据当前 CPU 处理器数量生成相应个数的线程池。它的特点是并行处理任务的,不能保证任务的执行顺序。

6.newFixedThreadPool 和 ThreadPoolExecutor 有什么关系?

答:newFixedThreadPool 是 ThreadPoolExecutor 包装,newFixedThreadPool 底层也是通过 ThreadPoolExecutor 实现的。

newFixedThreadPool 的实现源码如下:

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {

return new ThreadPoolExecutor(nThreads, nThreads,

0L, TimeUnit.MILLISECONDS,

new LinkedBlockingQueue(),

threadFactory);

}

7.单线程的线程池存在的意义是什么?

答:单线程线程池提供了队列功能,如果有多个任务会排队执行,可以保证任务执行的顺序性。单线程线程池也可以重复利用已有线程,减低系统创建和销毁线程的性能开销。

8.线程池为什么建议使用 ThreadPoolExecutor 创建,而非 Executors?

答:使用 ThreadPoolExecutor 能让开发者更加明确线程池的运行规则,避免资源耗尽的风险。

Executors 返回线程池的缺点如下:FixedThreadPool 和 SingleThreadPool 允许请求队列长度为 Integer.MAX_VALUE,可能会堆积大量请求,可能会导致内存溢出;

CachedThreadPool 和 ScheduledThreadPool 允许创建线程数量为 Integer.MAX_VALUE,创建大量线程,可能会导致内存溢出。

总结

Executors 可以创建 6 种不同类型的线程池,其中 newFixedThreadPool() 适合执行单位时间内固定的任务数,newCachedThreadPool() 适合短时间内处理大量任务,newSingleThreadExecutor() 和 newSingleThreadScheduledExecutor() 为单线程线程池,而 newSingleThreadScheduledExecutor() 可以执行周期性的任务,是 newScheduledThreadPool(n) 的单线程版本,而 newWorkStealingPool() 为 JDK 8 新增的并发线程池,可以根据当前电脑的 CPU 处理数量生成对比数量的线程池,但它的执行为并发执行不能保证任务的执行顺序。

下一篇:ThreadLocal详解在公众号菜单中可自行获取专属架构视频资料,包括不限于 java架构、python系列、人工智能系列、架构系列,以及最新面试、小程序、大前端均无私奉献,你会感谢我的哈

往期精选

java executors 详解_线程池—Executors 详解相关推荐

  1. java executors 详解_线程池Executors详解

    为什么要用线程池呢? 一是减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务; 二是可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为因为消耗过多的内存,而把服务器累 ...

  2. java线程池详解及五种线程池方法详解

    基础知识 Executors创建线程池 Java中创建线程池很简单,只需要调用Executors中相应的便捷方法即可,比如Executors.newFixedThreadPool(int nThrea ...

  3. Java线程池ThreadPool详解

    Java线程池ThreadPool详解 1. 线程池概述 1.1 线程池简介 1.2 线程池特点 1.3 线程池解决问题 2. 线程池原理分析 2.1 线程池总体设计 2.6 线程池流转状态 2.2 ...

  4. Java通过Executors提供四种线程池

    http://cuisuqiang.iteye.com/blog/2019372 Java通过Executors提供四种线程池,分别为: newCachedThreadPool创建一个可缓存线程池,如 ...

  5. java中executors_Java通过Executors提供四种线程池

    http://cuisuqiang.iteye.com/blog/2019372 Java通过Executors提供四种线程池,分别为: newCachedThreadPool创建一个可缓存线程池,如 ...

  6. async spring 默认线程池_Spring boot注解@Async线程池实例详解

    这篇文章主要介绍了Spring boot注解@Async线程池实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 从Spring3开始提供了@A ...

  7. ThreadPoolExecutor详解及线程池优化

    前言 ThreadPoolExecutor在concurrent包下,是我们最常用的类之一.无论是做大数据的,还是写业务开发,对其透彻的理解以及如何发挥更好的性能,成为了我们在更好的coding道路上 ...

  8. 线程池源代码详解,参数详解

    线程池源代码详解,参数详解 ThreadPoolExecutor 构造函数源代码 public ThreadPoolExecutor(int corePoolSize, int maximumPool ...

  9. java阻塞线程池_线程池解决阻塞方法

    一.序言 当我们需要使用线程的时候,我们可以新建一个线程,然后显式调用线程的start()方法,这样实现起来非常简便,但在某些场景下存在缺陷:如果需要同时执行多个任务(即并发的线程数量很多),频繁地创 ...

最新文章

  1. java mybatis XML文件中大于号小于号转义(转载)
  2. html颜色叠加代码,html代码大全(基础使用代码)(颜色代码完整版)
  3. fir滤波器课程设计matlab,Matlab课程设计---FIR数字滤波器
  4. 解决SpringMVC中文乱码问题 -----这是服务器返回参数到前端中文乱码
  5. linux搭建java开发环境_Linux搭建Java开发环境
  6. java web事务控制_JavaWeb学习之事务
  7. loadrunner中变量转换成一个参数
  8. php ueditor怎么用,ueditor PHP版本使用方法
  9. 数据结构详解之栈和队列
  10. iocomp入门教程(绘制Plot)
  11. 光头老熊做易赛是这样做教下家的
  12. 打通Linux脉络系列:进程、线程和调度-宋宝华-专题视频课程
  13. 宏基 4560G笔记本 AMD APU A6-3400试用报告
  14. 产品管理 OKR:最佳实践和示例
  15. 实验四 负反馈放大电路
  16. 物联卡先用流量包还是套餐流量,物联卡流量扣除顺序是什么?
  17. 北航计算机网络安全,李舟军
  18. python程序代码
  19. 主机和虚拟机通过虚拟串口通信
  20. 一款开源 OA 办公自动化系统

热门文章

  1. Go 操作 excel 利器之 excelize
  2. 2018年IT名企校园招聘会时间表
  3. php微信分享文章怎么做的,微信开发使用 JSSDK 自定义分享内容 - 文章教程
  4. Android 重启系统设备 或APP
  5. Java模拟浏览器工具HtmlUnit-登陆实践
  6. keepalived 防火墙配置
  7. 完整的微信小程序支付开发记录(亲测)
  8. 为什么越来越喜欢用绍兴大带宽服务器
  9. 苹果证书p12和描述文件的创建方法
  10. DDD(domain driven design)-领域驱动设计