提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
前言
提示:以下内容是自己学习与总结,如有不准确的地方,欢迎指点。
涉及内容:
ThreadPoolExecutor
ExecutorService
ThreadPoolTaskExecutor
一.ExecutorService创建线程池(最原始)
//JDK提供
//工厂类Executors中也使用到该类线程池
ExecutorService pool=new ThreadPoolExecutor(
0, //corePoolSize = 0
Integer.MAX_VALUE, //maximumPoolSize = 2147483647
60L, //keepAliveTime = 60
TimeUnit.MILLISECONDS,
new SynchronousQueue<Runnable>(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
二.工厂类Executors快速创建线程池(最简单)
Executors:线程池的工具类,相当于一个工厂类,用来创建合适的线程池,返回ExecutorService类型的线程池,由java.util提供
分别是:缓存线程池、固定大小线程池、单线程线程池、可延时或定时执行的线程池
##1.可缓存线程池:
//适用于大量短耗时任务和对响应时间要求较高的场景
//允许的创建线程数量为 Integer.MAX_VALUE(无限),可能会创建大量的线程
//添加任务时会优先使用空闲的线程,如果没有就创建一个新线程,线程数没有上限,所以每一个任务都会马上被分配到一个工作线程进行执行,不需要在阻塞队列中等待;
//如果线程池长期闲置,那么其中的所有线程都会被销毁,节约系统资源。
ExecutorService executorService=Executors.newCachedThreadPool();
| 优点 | 缺点 |
|---|---|
| 1.任务在添加后可以马上执行,不需要进入阻塞队列等待; 2.在闲置时不会保留线程,可以节约系统资源 | 对线程数没有限制,可能会过量消耗系统资源; |
2.定长线程池:
//任务量峰值不会过高,且任务对响应时间要求不高的场景
//定长线程池中的线程数会逐步增长到nThreads个,并且在之后空闲线程不会被释放,线程数会一直保持在nThreads个。
//如果添加任务时所有线程都处于忙碌状态,那么就会把任务添加到阻塞队列中等待执行,阻塞队列中任务的总数没有上限。
ExecutorService executorService=Executors.newFixedThreadPool(int nThreads)
| 优点 | 缺点 |
|---|---|
| 线程数固定,对系统资源的消耗可控 | 1.任务量暴增的情况下线程池不会弹性增长,会导致任务完成时间延迟; 2.使用了无界队列,在线程数设置过小的情况下可能会导致过多的任务积压,引起任务完成时间过晚和资源被过度消耗的问题 |
3.延时任务线程池:
//可以提供延时执行、定时执行等功能
//实现了ScheduledExecutorService接口,主要用于需要延时执行和定时执行的情况。
ScheduledExecutorService executorService=Executors.newScheduledThreadPool(int corePoolSize);
| 优点 | 特点 |
|---|---|
| 可以提供延时执行、定时执行等功能 | 1.最大线程数为无限,在任务量较大时可以创建大量新线程执行任务; 2.超时时间为0,线程空闲后会被立即销毁; 3.使用了延时工作队列,延时工作队列中的元素都有对应的过期时间,只有过期的元素才会被弹出; |
4.单线程线程池:
//单线程线程池中只有一个工作线程,可以保证添加的任务都以指定顺序执行(先进先出、后进先出、优先级)
ExecutorService executor<br> =Executors.ScheduledThreadPool();
| 优点 | 特点 |
|---|---|
| 可以保证添加的任务都以指定顺序执行 | 相比与手动创建线程:1.可以通过共享的线程池很方便地提交任务进行异步执行,而不用自己管理线程的生命周期; 2.我们可以使用任务队列并指定任务的执行顺序,容易对任务进行管理 |
三.ThreadPoolTaskExecutor简洁创建线程池(常用)
由Spring提供,可通过配置和自动注入的方式来构建线程池其底层也是适应使用的Executor,但提供的方法更多、可配置的参数也相对丰富:
ThreadPoolTaskExecutor poolTaskExecutor=new ThreadPoolTaskExecutor();
poolTaskExecutor.setQueueCapacity(0); //这个地方随便填
poolTaskExecutor.setCorePoolSize(0); //核心线程数是0,阻塞队列会选择new SynchronousQueue()
poolTaskExecutor.setMaxPoolSize(2147483647); // Integer.MAX_VALUE = 2147483647
poolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
poolTaskExecutor.setKeepAliveSeconds(60);
poolTaskExecutor.initialize();
四.总结
建议:
-
1.线程池尽量不要使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,因为Executors 返回的线程池对象的弊端如下:
** 1) FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE(无限),可能会堆积大量的请求,从而导致 OOM。
** 2) CachedThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE(无限),可能会创建大量的线程,从而导致 OOM。 -
2 线程的执行方式的区别
execute(…) 方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功。
submit(…) 方法用于提交需要返回值的任务。
线程池会返回一个 future 类型的对象,通过这个 future 对象可以判断任务是否执行成功,并且可以通过 future 的 get() 方法来获取返回值,get() 方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
五.实践
提示:线程池的实践,处理了单一线程线程池、多线程线程池、并发、异步
点击下面超链接查看:
使用单一线程线程池、多线程线程池、异步线程,实现作业的同步和异步执行,并实现高并发的处理。
本文深入探讨了Java线程池的创建与使用,包括ExecutorService的原始创建,Executors工厂类的四种线程池类型,以及Spring提供的ThreadPoolTaskExecutor。详细分析了各种线程池的优缺点,强调了避免使用Executors创建线程池的原因,并提出了线程执行方式的差异。最后,给出了线程池实践中的应用场景和注意事项。

376

被折叠的 条评论
为什么被折叠?



