非常适合新手学生的Java线程池超详细分析
作者:摸鱼打酱油 发布时间:2023-03-03 11:59:54
线程池的好处
可以实现线程的复用,避免重新创建线程和销毁线程。创建线程和销毁线程对CPU的开销是很大的。
可以限制最大可创建的线程数,可根据自己的机器性能动态调整线程池参数,提高应用性能。
提供定时执行、并发数控制等功能。
统一管理线程。
创建线程池的五种方式
1:缓存线程池(不推荐)
2:固定容量线程池(不推荐)
3:单个线程池(不推荐)
4:定时任务线程池(不推荐)
5:通过ThreadPoolExecutor构造方法创建线程池(阿里巴巴开发手册十分推荐)
前面4种创建线程池的方式都是通过Executors的静态方法来创建。
缓存线程池CachedThreadPool
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
final int finalI = i;
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
}
});
}
为什么不推荐使用缓存线程池?
源码分析
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
}
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), defaultHandler);
}
通过上面两个代码片段,我们可以看出CachedThreadPool的maximumPoolSize为Integer的最大值2147483647,相当于可以无限的创建线程,而创建线程是需要内存的,这样就会造成内存溢出,而且一般的机器也没用那么大的内存给它创建这么大量的线程。
固定容量线程池FixedThreadPool
newFixedThreadPool(int num),num就是我们要指定的固定线程数量
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
final int finalI = i;
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
}
});
}
输出:
pool-1-thread-5<thread->run>4
pool-1-thread-4<thread->run>3
pool-1-thread-5<thread->run>5
pool-1-thread-3<thread->run>2
pool-1-thread-3<thread->run>8
pool-1-thread-3<thread->run>9
pool-1-thread-2<thread->run>1
pool-1-thread-1<thread->run>0
pool-1-thread-5<thread->run>7
pool-1-thread-4<thread->run>6
可以看出起到了线程的复用。
为什么FixedThreadPool是固定线程池?
源码分析
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
}
通过这个源码可以看出,核心线程数(corePoolSize)和最大线程数(maximumPoolSize)都为nThreads,因为只有这样,线程池才不会进行扩容,线程数才固定。
单个线程池SingleThreadExecutor
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
final int finalI = i;
executorService.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
}
});
}
为什么SingleThreadExecutor只含有一个线程?
源码分析
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
}
通过这个源码可以看出,核心线程数(corePoolSize)和最大线程数(maximumPoolSize)都为1,所以它只含有一个线程。
定时任务线程池ScheduledThreadPool
int initDelay=10; //初始化延时
int period=1;//初始化延迟过了之后,每秒的延时
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"<thread->run>");
}
},initDelay,period, TimeUnit.SECONDS);
这段代码的效果是:程序运行之后等10秒,然后输出第一次结果,之后每隔1秒输出一次结果。
为什么不推荐使用ScheduledThreadPool?
源码分析
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, 2147483647, 10L, TimeUnit.MILLISECONDS, new ScheduledThreadPoolExecutor.DelayedWorkQueue());
}
可以看出ScheduledThreadPool的最大线程数(maximumPoolSize)为Integer的最大值2147483647,相当于可以无限的创建线程,而创建线程是需要内存的,这样就会造成内存溢出,而且一般的机器也没用那么大的内存给它创建这么大量的线程。
ThreadPoolExecutor创建线程池(十分推荐)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 12; i++) {
final int finalI = i;
threadPoolExecutor.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
}
});
}
ThreadPoolExecutor的七个参数详解
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
}
corePoolSize:核心线程数。这些线程一旦被创建不会被销毁,是一直存在的。线程池默认是没有线程的,当有任务到来了,就会通过ThreadFactory去创建线程,并一直存在。
maximumPoolSize:最大线程数。非核心线程数=maximumPoolSize-corePoolSize,非核心线程数其实就是可扩容的线程数,可能会被销毁。
keepAliveTime:非核心线程的空闲存活时间。当通过扩容生成的非核心线程数在keepAliveTime这个时间后还处于空闲状态,则会销毁这些非核心线程。
unit:keepAliveTime的时间单位,例如:秒
workQueue:等待区。当来了>corePoolSize的任务时会把任务存放在workQueue这个阻塞队列中,等待其他线程处理。
threadFactory:线程工厂。创建线程的一种方式。
handler:拒绝策略。当来了>最大线程数+workQueue的容量则会执行拒绝策略
workQueue
ArrayBlockingQueue:有界阻塞队列。队列有大小限制,当容量超过时则会触发扩容或者拒绝策略。
public ArrayBlockingQueue(int capacity) {
this(capacity, false);
}
LinkedBlockingQueue: * 阻塞队列,队列无大小限制,可能会造成内存溢出。
public LinkedBlockingQueue() {
this(2147483647);
}
handler
AbortPolicy:直接抛异常
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString());
}
}
DiscardPolicy:不作任何操作。默默丢弃任务
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy:丢掉存在时间最长的任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
CallerRunsPolicy:让提交任务的线程去处理任务
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() {
}
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
threadFactory
ThreadFactory threadFactory = Executors.defaultThreadFactory();
threadFactory.newThread(new Runnable() {
@Override
public void run() {
System.out.println("threadFactory");
}
}).start();
如何触发拒绝策略和线程池扩容?
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20,
2L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
for (int i = 0; i < 26; i++) { //并发数26
final int finalI = i;
threadPoolExecutor.execute(new Runnable() {
public void run() {
System.out.println(Thread.currentThread().getName()+"<thread->run>"+ finalI);
}
});
}
/**
* 核心线程数=10,最大线程数=20,故可扩容线程数=20-10
* BlockingQueue的大小为5,故等待区的大小为5,也就是当并发数<=核心线程数+5不会扩容,并发数大于16才会扩容
*
* 触发扩容:并发数>核心线程数+阻塞队列的大小
* 对于这段代码,如果来了26个并发,10个并发会被核心线程处理,5个会在等待区,剩下11个会因为等待区满了而触发扩容
* 因为这里最多能够扩容10个,这里却是11个,所以会触发拒绝策略
*/
为什么这段代码会触发拒绝策略
对于这段代码,如果来了26个并发,10个并发会被核心线程处理,5个会在等待区,剩下11个会因为等待区满了而触发扩容,但是又因为因为这里最多能够扩容10个,这里却是11个,所以会触发拒绝策略。
怎么触发扩容
触发扩容:并发数>核心线程数(corePoolSize)+阻塞队列(workQueue)的大小
使用Java纯手写一个线程池
下期文章链接https://www.jb51.net/article/241589.htm
来源:https://blog.csdn.net/weixin_50071998/article/details/123552661


猜你喜欢
- JAVA用于开发图形界面应用的 SWING 组件包功能强大,使用方便。接下来我们就使用其写一个简单的图形界面小程序:加法计算器。第一步:首先
- 本文为大家汇总了Android Studio ADB网络调试的使用方法,供大家参考,具体内容如下随着技术的发展,现在的安卓手机大部分开始使用
- 一、java端首先我使用的是java自带的对webservice的支持包来编写的服务端和发布程序,代码如下。webservice的接口代码:
- 现在版本更新有两种处理方式:跳转到App应用市场,通过应用市场下载更新安装。在App内进行Apk下载,下载完成后更新安装。实现思路:请求后台
- 前言说实话当第一次看到这个需求的时候,第一反应就是Canvas只有drawLine方法,并没有drawDashLine方法啊!这咋整啊,难道
- 欲达此目的,可以采用下列两种作法: ◆使用XmlConvert类。 ◆将一个XSLT转换套用至DataSet数据的XML表示。 程序范例 本
- Android 侧滑菜单的实现,参考网上的代码,实现侧滑菜单。最重要的是这个动画类UgcAnimations,如何使用动画类来侧滑的封装Fl
- 一、方法的定义1.方法体中最后返回值可以使用return, 如果使用了return, 那么方法体的返回值类型一定要指定2.如果方法体重没有r
- 在Android的API中可以发现有很多用整数集来作为参数的地方,先来看一下实例。LinearLayout是大家所熟知的一个UI基本元素,它
- 本文实例为大家分享了Unity打印机打印图片的具体代码,供大家参考,具体内容如下1、调用打印机首先就是要配置好打印机 就是电脑跟打印机已经连
- 本文实例讲述了Android开发之App widget用法。分享给大家供大家参考,具体如下:放在桌面上的控件叫做——App widget,例
- 前言这几天学习谷粒商城又再次的回顾了一次SpringCache,之前在学习谷粒学院的时候其实已经学习了一次了!!!这里就对自己学过来的内容进
- 遇到的问题!注:自定义CommentGenerator的都知道通过实现CommentGenerator接口的一些不足,毕竟只是实现了Comm
- 前言本文主要介绍下Spring事务中的传播行为。事务传播行为是Spring框架独有的事务增强特性,他不属于的事务实际提供方数据库行为。这是S
- maven运行依赖于 JAVA_HOME如果各位还没有配置 JAVA_HOME,可以参考我的另一篇博客 JDK环境变量配置 JDK 环境变量
- springcloud-gateway集成knife4j环境信息环境信息spring-boot:2.6.3spring-cloud-alib
- 目录准备工作1. 引入pom依赖2. 实现功能Excel文件下载3. 日志实体类4. 接口和具体实现Excel文件导入5. 文件读取配置6.
- 本文介绍了 SpringBoot之Controller的使用,分享给大家,具体如下:1.@Controller:处理http请求 2.@Re
- 前面照着android系统的裁剪图片的功能自己写了一个相似的工具。功能是大体上实现了,但留下了一个调用的问题:如何从我的程序调用这个裁剪工具
- MyBatis * 介绍MyBatis提供了一种插件(plugin)的功能,虽然叫做插件,但其实这是 * 功能。那么 * 拦截MyBatis