目录
1、ThreadPoolExecutor和ThreadPoolTaskExecutor的区别对比
3、向线程池提交任务时,execute方法和submit方法有什么区别?
一、线程池的概述
1、线程池是什么?我们为什么使用线程池?
线程池是一种优化多线程管理的技术,它通过池化技术思想来复用和管理线程,从而提高程序性能和降低资源消耗,类似于我们的数据库连接池,都是为了提升性能,节省开销,线程池的使用有以下原因:
* 减少资源消耗:避免频繁创建和销毁线程所带来的性能开销。
* 提高响应速度:任务可以直接由空闲线程执行,无需等待新线程创建。
* 控制并发数:限制同时运行的线程数量,防止资源耗尽。
* 管理性:提供对线程生命周期的统一管理和监控。
简而言之,线程池有助于优化多线程应用的性能、资源使用和可管理性。
2、线程池的创建有哪些方法?
Java中的线程池主要通过java.util.concurrent.ExecutorService接口及其实现类ThreadPoolExecutor进行构建。以下是常见的几种构建方法:
1、使用Executors类的工厂方法:Executors类提供了一系列用于创建线程池的静态工厂方法,例如newFixedThreadPool、newCachedThreadPool。
2、直接创建ThreadPoolExecutor实例:可以直接实例化ThreadPoolExecutor类并传入一系列参数来创建线程池,例如核心线程数、最大线程数、空闲线程存活时间、时间单位、任务队列参数设置。(推荐这种方式)
3、线程池的拒绝策略有哪些?
线程池的拒绝策略当线程池中的线程达到最大线程数且任务队列已满时,线程池会采用拒绝策略来处理新到来的任务。
1、AbortPolicy(默认拒绝策略):直接抛出RejectedExecutionException异常,表示任务无法处理。
2、CallerRunsPolicy:调用任务的提交者所在的线程来执行任务。这种策略可以降低新任务提交的速度,从而为线程池腾出时间处理已提交的任务。(推荐使用)
3、DiscardPolicy:该策略直接丢弃新到来的任务,不抛出异常。如果你可以容忍某些任务不被执行,这种策略可能会很有用。
4、DiscardOldestPolicy:该策略会丢弃任务队列中等待时间最长的任务,然后尝试提交新任务。这种策略在任务队列可能存在低优先级任务时较为实用。
使用 CallerRunsPolicy 策略,三个任务,只有一个线程连接执行,且任务队列只能等一个任务,当使用该策略时,第三个任务由主线程 main 调用了,执行结果如下
执行结果:
二、在Spring中创建线程池哪种方式更加适用
1、ThreadPoolExecutor和ThreadPoolTaskExecutor的区别对比
ThreadPoolExecutor和ThreadPoolTaskExecutor在功能上相似,都是线程池的实现,但它们在使用场合、生命周期管理和扩展特性方面存在一些差异。
- 使用场合:ThreadPoolExecutor是Java标准库中提供的线程池实现,它是一个通用的线程池,可以用于各种场景。而ThreadPoolTaskExecutor是Spring框架提供的线程池实现,它专门针对Spring应用中的异步任务执行进行了优化和封装。
- 生命周期管理:ThreadPoolTaskExecutor由Spring容器管理其生命周期,这意味着当Spring应用上下文关闭时,ThreadPoolTaskExecutor会自动关闭其管理的线程池。而ThreadPoolExecutor不受Spring管理,需要手动管理其生命周期。
- 扩展特性:ThreadPoolTaskExecutor对ThreadPoolExecutor进行了增强,提供了更多的特性,如与Spring的集成、参数配置等。这些增强使得ThreadPoolTaskExecutor更适合在Spring环境中使用。
总的来说,虽然ThreadPoolExecutor和ThreadPoolTaskExecutor在本质上都是线程池的实现,但ThreadPoolTaskExecutor提供了更好的Spring集成和生命周期管理,使其成为Spring应用中推荐使用的线程池实现。
2、具体的创建按代码示例如下:
/**
* @author 安宁
*/
@Configuration
@Slf4j
public class ThreadPoolConfiguration {
@Bean("executor")
public Executor threadPool() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10);
executor.setMaxPoolSize(20);
executor.setQueueCapacity(3000);
executor.setKeepAliveSeconds(120);
executor.setThreadNamePrefix("mythread_");
// 设置拒绝策略-----调用任务的提交者所在的线程来执行任务。这种策略可以降低新任务提交的速度,从而为线程池腾出时间处理已提交的任务。
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
log.info("线程池{}启动成功!", "executor");
return executor;
}
}
以上方式我们创建了线程池,当SpringBoot加载的时候,将会创建bean然后初始化我们线程池,当需要执行任务时,我们可以将bean注入到需要的类当中进行使用,调用execute()方法执行我们任务 。
3、向线程池提交任务时,execute方法和submit方法有什么区别?
代码实现:
只需要改变这里的任务即可,可以时runnable类型的任务,也可以时callable类型的任务
这里的future的get方法是同步阻塞式,但是有一个设置时间的重载方法,当执行结果超过设定时间未返回,将会抛出异常,如下,我们可用线程休眠来演示超时。
submit两种任务都可以,而且callable本来就有返回值,而执行runnable的任务时,可以指定返回值
三、为什么不建议使用Executors去创建线程池?
首先Executors创建线程池,本质上是对ThreadpoolExecutor的一种封装,参数不如自定义的清晰,灵活。
不建议使用
Executors
去创建线程池,以下是一些具体的原因:
- 队列大小无限制:使用
Executors
创建的线程池,如newFixedThreadPool
和newSingleThreadExecutor
,会使用无界队列LinkedBlockingQueue
。这意味着如果提交的任务数量超过了线程池的处理能力,任务会在队列中无限堆积,可能导致内存溢出(OOM)。- 线程数量可能过多:使用
Executors
的newCachedThreadPool
和newScheduledThreadPool
方法创建的线程池,它们的最大线程数是Integer.MAX_VALUE
。在高并发情况下,这可能会导致创建大量的线程,同样有可能造成内存溢出(OOM)。- 无法精确控制线程池参数:使用
Executors
类的方法创建线程池时,不能对线程池的核心参数进行精确的控制,例如核心线程数、最大线程数、线程空闲时间等。而通过直接使用ThreadPoolExecutor
可以对这些参数进行明确的设置,从而更好地管理线程资源。- 可能的资源耗尽风险:由于
Executors
创建的线程池可能存在上述问题,因此它们在实际使用中可能会导致系统资源的不稳定甚至耗尽,特别是在高负载的情况下。综上所述,为了避免这些潜在的问题,建议直接使用
ThreadPoolExecutor
来创建线程池,这样可以更加明确地控制线程池的行为,避免资源耗尽的风险,并提高应用程序的稳定性和可靠性。