一、线程池概述
线程池提供了一个线程队列,队列中保存着所有等待状态的线程。通过避免频繁创建与销毁线程带来的额外开销,线程池能够显著提高系统的响应速度。本文将详细介绍一下线程池相关概念及如何使用线程池,欢迎大家评论指点。
二、线程池的体系结构
首先先介绍一下线程池的体系结构,主要有如下接口类。
- java.util.concurrent.Executor:负责线程的使用与调度的根接口。
- ExecutorService 子接口:线程池的主要接口。
- ThreadPoolExecutor:线程池的实现类。
- ScheduledExecutorService 子接口。
- ScheduledThreadPoolExecutor:继承自 ThreadPoolExecutor,实现了 ScheduledExecutorService 接口。
三、工具类:Executors
首先创建线程池的第一种方式是使用工具类Executors,主要如下几种方式:
- Executors.newFixedThreadPool():创建固定大小的线程池。
- Executors.newCachedThreadPool():创建缓存线程池,线程数量不固定,可以根据需求自动调整。
- Executors.newSingleThreadExecutor():创建单个线程池,线程池中只有一个线程。
- Executors.newSingleThreadScheduledExecutor():创建固定大小的线程,可以延迟或定时执行任务。
四、使用 ThreadPoolExecutor 自定义创建多线程
接下来就开始自定义创建多线程,主要是通过 ThreadPoolExecutor 的构造函数,可以自定义创建多线程。构造函数包括5个基本参数和2个可选参数:
- corePoolSize:核心池的大小,即线程池维护线程的最少数量。
- maximumPoolSize:线程池中线程的最大数量。
- keepAliveTime:线程池维护线程所允许的空闲时间。
- unit:线程池维护线程所允许的空闲时间的单位。
- workQueue:线程池所使用的缓冲队列。
- handler:线程池对拒绝任务的处理策略(可选参数)。
- threadFactory:自定义线程工厂(可选参数),可以使用 com.google.guava 的 ThreadFactoryBuilder 来指定线程名称。 创建代码如下:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
4,
4,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>(10),
threadFactory,// 线程工厂可以不用传参
new ThreadPoolExecutor.CallerRunsPolicy()//线程拒绝策略,也可以不用传参
);
五、线程池 ThreadPoolExecutor 中的 execute 和 submit 方法对比
- 定义方法的类不同:submit 在 ExecutorService 接口中定义,而 execute 方法在 Executor 类中定义,ThreadPoolExecutor 实现了这两个接口。
- 返回值类型不同:execute 方法返回值为空,而 submit 方法会以 Future 的形式返回线程的执行结果。
- 对异常的处理方式不同:execute 方法会直接打印产生的异常堆栈,而 submit 方法提交的子线程产生异常时,调用 Future 实例的 get 方法可以在主线程捕获异常。
execute接口方法
public interface Executor { void execute(Runnable command); }
submit方法
Future submit(Runnable task);
Future submit(Runnable task, T result);
Future submit(Callable task);
六、项目应用(使用场景)
- 并发量大的场景:如批量查询,通过多线程(线程池)分配处理任务,提高系统性能。 比如下面案例代码:
//List分批处理
List> productList = Lists.partition(activityProductBatchSaveDTO.getProductIds(), 15);
long time = System.currentTimeMillis();
//
Map productFullsById = new ConcurrentHashMap<>();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(productList.size(),productList.size(),1, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100000),
new ThreadFactoryBuilder().setNameFormat("OMG-Tool-%d").build(),
new ThreadPoolExecutor.CallerRunsPolicy());
CountDownLatch countDownLatch =new CountDownLatch(productList.size());
productList.forEach(productIds->{
threadPoolExecutor.execute(()->{
ProductIdRequest request = BeanMapper.map(req, ProductIdRequest.class);
request.setProductIds(productIds);
productFullsById.putAll(productFacade.getProductsByIdsV3(request));
countDownLatch.countDown();
});
});
try {
countDownLatch.await();
}catch (Exception e){
}finally {
threadPoolExecutor.shutdown();
}
- 耗时较长的任务:如用户注册后发送邮件、短信等操作,可以使用异步线程处理,即使任务失败影响也不大。
- 定时任务:如定期更新配置文件、备份数据等任务,可以使用定时线程池执行。
总结
总的来说,利用线程池可以动态配置线程池相关参数,包括核心线程池和非核心线程池。当内存紧张时,可以释放部分线程或关闭非核心线程池。线程池具有以下优点:
- 降低资源销毁:重用存在的线程,减少对象创建销毁的开销。
- 提高线程管理性:有效控制最大并发线程数、提高系统资源的使用率,避免过多资源竞争和阻塞。
- 提高响应速度:任务到达时,无需创建线程即可执行。提供定时执行、定期执行、单线程、并发数控制等功能。