Java高并发,ThreadPoolExecutor线程池技术
Java当中的线程池是通过Executor这个框架接口来实现的,该框架当中用到了Executor,Executors工具类,ExecutorService,ThreadPoolExecutor

Executors创建线程的三种方法:
ExecutorService threadPool = Executors.newFixedThreadPool(5); //固定容量
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //单例的、单个线程的线程池
ExecutorService threadPool = Executors.newCachedThreadPool(); //缓存的 即超出就自动创建线程的
接下来讲解一下这三个的区别:
固定容量的线程池
首先我们看的是第一个固定容量的线程池Executors.newFixedThreadPool(5);:
首先看代码:
/**
* 主要特点:线程复用;控制最大并发数;管理线程。
*
* @author Cocowwy
* @create 2020-05-05-20:20
* Executor/ExecutorServic(Interface)
* Executors 线程池的工具类
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
//一池五个受理线程
ExecutorService threadPool = Executors.newFixedThreadPool(5); //看源码是LinkedBlockingQueue<Runnable>()
try {
//模拟10个用户办理业务,但是只有5个受理窗口
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
});
Thread.sleep(400);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
} }
结果如下:

接着我们加上一句线程睡眠一小会的代码:
public class MyThreadPoolDemo {
public static void main(String[] args) {
//一池五个受理线程
ExecutorService threadPool = Executors.newFixedThreadPool(5); //看源码是LinkedBlockingQueue<Runnable>()
try {
//模拟10个用户办理业务,但是只有5个受理窗口
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
});
Thread.sleep(400);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
}

在这里我们可以看到有序办理了每个业务。可以看出这个是固定了大小的线程池,每次都是从这个线程池中取的线程。
单例的线程池
这是第二个,单例的线程池:
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //一池1个受理线程
public class MyThreadPoolDemo {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newFixedThreadPool(5); //一池五个受理线程,看源码是LinkedBlockingQueue<Runnable>()
ExecutorService threadPool = Executors.newSingleThreadExecutor(); //一池1个受理线程
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
}

我们可以看到一直是一个线程在受理业务。
可扩展的线程池
接下来是第三个线程池:ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N个受理线程 可扩展的
ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N个受理线程 可扩展的
接下来上代码:
public class MyThreadPoolDemo {
public static void main(String[] args) {
// ExecutorService threadPool = Executors.newFixedThreadPool(5); //一池五个受理线程,看源码是LinkedBlockingQueue<Runnable>()
// ExecutorService threadPool = Executors.newSingleThreadExecutor(); //一池1个受理线程
ExecutorService threadPool = Executors.newCachedThreadPool(); //一池N个受理线程
try {
for (int i = 0; i < 10; i++) {
threadPool.execute(() -> {
try {
Thread.sleep(400);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
}
先看看效果:

在上面的代码中,我们可以发现的是,对从线程池取的线程睡了0.4s,然而却可以发现创建出了3,6,9,10,1,8,7,4,5.....这么多的线程
因为我们睡眠的时间太短了,表明需要受理的业务频率太多,所以才开辟了这么多的线程去处理。
tips:我们可以设置睡眠时间,来控制线程池的开辟数量。当我们将睡眠时间设置的尽可能的大,那么开辟的线程数自然而然的就少了下来,证明需要受理的业务不那么频繁
所以我们可以发现当请求过多,过于频繁的时候使用可扩展的线程池newCachedThreadPool将会创建更多的线程。
线程池的源码
首先点进newFixedThreadPool()的源码可以看到:

接下来点进去newSingleThreadExecutor()的源码可以看到:

接下来点进去newCachedThreadPool()的源码可以看到:

综上所述,返回的实际上只是一个ThreadPoolExecutor(可以看看继承图),利用构造器传入的不同的参数而已,而且我们也能发现底层是阻塞队列。
同时说明我们也可以通过ThreadPoolExecutor`来创建线程池,Executors只是一个创建线程池的工具类,实际上返回的还是ThreadPoolExecutor。
ThreadPoolExecutor的七大参数
接着我们继续点进ThreadPoolExecutor:

接着再点进这this,我们可以看到它有七个参数,:corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory,handler

下图是这七大参数的解释:

线程池的底层工作原理图
接下来结合下图理解理解上述的7大参数:
首先看看线程池的底层工作原理图:

看上图以及参数解析对照我们可以知道maximumPool包含corePool,maximumPool表示最多能放的线程数,而corePool表示的就是线程的常驻数,可以理解为银行的有最多有5个受理窗口,但是常用的却只有2个。
而候客区就相当于我们的阻塞队列(BlockingQueue),那当我们的阻塞队列满了之后,handle拒绝策略出来了,相当于银行门口立了块牌子,上面写着不办理后面的业务了!
然后当客户都办理的差不多了,此时多出来(在corePool的基础上扩容的窗口)的窗口在经过keepAliveTime的时间后就关闭了,重新恢复到corePool个受理窗口。
总结一下线程池的工作流程:
首先线程池接收到任务,先判断核心线程数是否满了,如果corepool没有满接客则核心(常驻)线程处理。
常驻线程满了就放到阻塞队列,如果阻塞队列没满,这些任务放在阻塞队列。
如果阻塞队列也满了,就扩容线程数到最大线程数。
如果最大线程数也满了,就是我们的拒绝策略。
这就是线程池四大步骤。 接客、放入队列,扩容线程,拒绝策略!
也可以看下图流程解释:太妙了!!

实际开发当中如何合适的使用线程池

为什么不建议使用Executors工具类去创建线程池?
举个例子,回到之前讲的 newSingleThreadExecutor(); ;以及Executors.newCachedThreadPool( );创建的线程池,看看源码,
正如源码中看到的那样:

如果用Executors去创建,默认的Integer.MAX_VALUE的大小是21亿............极大的消耗内存,线程池永远不会慢,内存会被你压爆
又如下面源码:
//这是Single的
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
}
//点进去LinkedBlockQueue
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
} //这是Cahed的
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
对于newSingleThreadExecutor()而言,LinkedBlockQueue的长度是Integer.MAX_VALUE,
对于newCachedThreadPool()而言,maximumPool的值竟然为Integer.MAX_VALUE!!
两者均会导致OOM异常!
自定义线程池
public class MyThreadPoolDemo {
public static void main(String[] args) {
//自定义线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(3), //不写的话默认也是Integer.MAX_VALUE
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());//默认的拒绝策略
try {
//模拟10个用户办理业务,但是只有5个受理窗口
for (int i = 0; i < 9; i++) {
threadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + "\t" + "办理业务");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //关闭线程池
}
}
threadPool 是我们自定义的线程池,连接过上面的参数的应该都知道。
该线程池最大支持的并发量就应该是maximumPool+Queue的大小,即5+3=8,而超过了大小之后就会报错:java.util.concurrent.RejectedExecutionException 拒绝执行异常

线程池的四大拒绝策略
接下来我们看看线程池的四大拒绝策略,上述是JDK默认的拒绝策略:

接下来看看另外三种策略的运行结果
将上述代码的拒绝策略改成第二种new ThreadPoolExecutor.CallerRunsPolicy(),回退到原始调用者,这里之main线程

第三种new ThreadPoolExecutor.DiscardOldestPolicy():不报错。

第四种new ThreadPoolExecutor.DiscardPolicy(): 同样不报错。

以上策略均继承自RejectedExecutionHandler接口。
怎么设置maximumPoolSize合理
最后提一句怎么设置maximumPoolSize合理,
了解:IO密集型,CPU密集型:(调优)
1、CPU 密集型,一般设置为CPU核数加1,可以保持CPu的效率最高!
System.out.println(Runtime.getRuntime().availableProcessors()); //获取CPU的核数,8核
2、IO 密集型, 判断你程序中十分耗IO的线程
Java高并发,ThreadPoolExecutor线程池技术的更多相关文章
- java高并发之线程池
Java高并发之线程池详解 线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对 ...
- Java并发——ThreadPoolExecutor线程池解析及Executor创建线程常见四种方式
前言: 在刚学Java并发的时候基本上第一个demo都会写new Thread来创建线程.但是随着学的深入之后发现基本上都是使用线程池来直接获取线程.那么为什么会有这样的情况发生呢? new Thre ...
- 你真的懂ThreadPoolExecutor线程池技术吗?看了源码你会有全新的认识
Java是一门多线程的语言,基本上生产环境的Java项目都离不开多线程.而线程则是其中最重要的系统资源之一,如果这个资源利用得不好,很容易导致程序低效率,甚至是出问题. 有以下场景,有个电话拨打系统, ...
- Java高性能并发编程——线程池
在通常情况下,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的 ...
- java之并发编程线程池的学习
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间. java.uitl.concurrent.Thre ...
- Linux + C + Epoll实现高并发服务器(线程池 + 数据库连接池)(转)
转自:http://blog.csdn.net/wuyuxing24/article/details/48758927 一, 背景 先说下我要实现的功能,server端一直在linux平台下面跑,当客 ...
- Java高并发之线程池详解
线程池优势 在业务场景中, 如果一个对象创建销毁开销比较大, 那么此时建议池化对象进行管理. 例如线程, jdbc连接等等, 在高并发场景中, 如果可以复用之前销毁的对象, 那么系统效率将大大提升. ...
- 1.6 JAVA高并发之线程池
一.JAVA高级并发 1.5JDK之后引入高级并发特性,大多数的特性在java.util.concurrent 包中,是专门用于多线程发编程的,充分利用了现代多处理器和多核心系统的功能以编写大规模并发 ...
- java线程池技术(二): 核心ThreadPoolExecutor介绍
版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程池技术属于比较"古老"而又比较基础的技术了,本篇博客主要作用是个人技术梳理,没什么新玩意. 一.Java线程池技术的 ...
- Java高并发 -- 线程池
Java高并发 -- 线程池 主要是学习慕课网实战视频<Java并发编程入门与高并发面试>的笔记 在使用线程池后,创建线程变成了从线程池里获得空闲线程,关闭线程变成了将线程归坏给线程池. ...
随机推荐
- TensorFlow和pytorch中的pin_memory和non_blocking设置是做什么的,又是否有用???(续2)
接前文: TensorFlow和pytorch中的pin_memory和non_blocking设置是做什么的,又是否有用??? TensorFlow和pytorch中的pin_memory和non_ ...
- H5页面\PC端实现QQ客服功能
1.背景 很多应用都有在线客服,最简单是实现就是利用人们常用的QQ 2.实现 步骤一:授权QQ通讯组件(普通QQ都是可以的) 授权链接:https://shang.qq.com/v3/widget.h ...
- .NET 开源权限认证项目 MiniAuth上线
前言 在Web应用项目中权限认证是个绕不开的话题,传统方法复杂又耗时.MiniAuth推出专为.NET开发者设计的简单.实用的权限认证项目. MiniAuth,作为ASP.NET Core的插件,让我 ...
- 免费word简历 简历制作平台
分享一个简历制作平台. 免费的word模版 链接地址 https://www.xyjianli.com/ https://www.xyjianli.com/list https://www.xyjia ...
- 需要多久才能看完linux内核源码?
代码中自由颜如玉!代码中自有黄金屋! 一.内核行数 Linux内核分为CPU调度.内存管理.网络和存储四大子系统,针对硬件的驱动成百上千.代码的数量更是大的惊人. 先说说最早的内核linux 0.11 ...
- 【CMake系列】10-cmake测试 ctest
cmake作为一个强大的构建系统指导工具,同时也提供了测试功能,可用于项目的单元测试等,也可以与其他测试框架协作,如googletest,共同完成项目开发中的测试工作,本节我们就来学习 如何借助cma ...
- 工作常用SQL
工作常用SQL Excel生成SQL 这个好用 ="insert into t_gk_mapping(id,gk_project_name,gk_project_code,main_proj ...
- Go 互斥锁 Mutex 源码分析(二)
原创文章,欢迎转载,转载请注明出处,谢谢. 0. 前言 在 Go 互斥锁 Mutex 源码分析(一) 一文中分析了互斥锁的结构和基本的抢占互斥锁的场景.在学习锁的过程中,看的不少文章是基于锁的状态解释 ...
- Swahili-text:华中大推出非洲语言场景文本检测和识别数据集 | ICDAR 2024
论文提出了一个专门针对斯瓦希里语自然场景文本检测和识别的数据集,这在当前研究中是一个未充分开发的语言领域.数据集包括976张带标注的场景图像,可用于文本检测,以及8284张裁剪后的图像用于识别. 来源 ...
- C# 使用 PaddleOCRSharp 识别 图片中的文字、 使用QRCoder生成二维码
目录 使用PaddleOCRSharp识别图片中的文字 PaddleOCRSharp资料汇总: 1.将源码编译成对应的.Net 版本引入到项目中 2. 初始化PaddleOCREngine 3. 调用 ...