线程池
线程池简介
线程池(ThreadPool) 是一种基于池化思想管理线程的工具。线程池维护多个线程,等待监督和管理分配可并 发执行的任务。看过new Thread源码后我们发现,频繁创建线程销毁线程的开销很大,会降低系统整体性能。
优点
➢ 降低资源消耗:通过线程池复用线程,降低创建线程和释放线程的损耗
➢ 提高响应速度:任务到达时,无需等待即刻运行
➢ 提高线程的可管理性:使用线程池可以进行统一的线程分配、调优和监控
➢ 提供可扩展性:线程池具备可扩展性,研发人员可以向其中增加各种功能,比如:延时、定时、监控等
使用场景
➢ 连接池:预先申请数据库连接,提升申请连接的速度,降低系统的开销(跨网络应用都需要线程池)
➢ 线程隔离:服务器接收大量请求,使用线程池来进行隔离处理
➢ 开发中,如需创建5以上线程,就可以考虑用线程池
线程池的核心参数
参数详解:
➢ corePoolSize:核心线程数,可以理解为空闲线程数,即便线程空闲(Idle),也不会回收
➢ maxPoolSize :最大线程数,线程池可以容纳线程的上限
➢ keepAliveTime :线程保持存活的时间,超过核心线程数的线程存活空闲时间超过keepAliveTime后就会被回收
➢ unit: 时间单位
➢ workQueue :工作队列,直接交换队列SynchronousQueue,无界队列LinkedBlockingQueue ,有界队列 ArrayBlockingQueue
➢ threadFactory:线程工厂,用来创建线程的工厂,线程都是出自于此工厂
➢ Handler:线程无法接收任务时的拒绝策略
线程池原理

① 提交任务,如果线程数小于corePoolSize即使其他线程处于空闲状态,也会创建一个新线程来运行任务
② 如果线程数大于corePoolSize,但少于maxPoolSize,将任务放入工作队列
③ 如果队列已满,并且线程数小于maxPoolSize,则创建一个新线程来运行任务。
④ 如果队列已满,并且线程数大于或等于maxPoolSize,则拒绝该任务。
自动创建线程
四种:
①newFixedThreadPool:固定数量线程池,无界任务阻塞队列
②newSingleThreadExecutor :一个线程的线程池,无界任务阻塞队列
③newCachedThreadPool :可缓存线程的无界线程池,可以自动回收多余线程
④newScheduledThreadPool :定时任务线程池
手动创建线程
如何设置线程池大小?
➢ CPU密集型:线程数量不能太多,可以设置为与相当于CPU核数-Runtime.getRuntime().availableProcessors()
➢ IO密集型:IO密集型CPU使用率不高,可以设置的线程数量多一些,可以设置为CPU核心数的2倍
拒绝策略:
➢ 拒绝时机:①最大线程和工作队列有限且已经饱和,②Executor关闭时
➢ 抛异常策略:AbortPolicy,说明任务没有提交成功
➢ 不做处理策略:DiscardPolicy,默默丢弃任务,不做处理
➢ 丢弃老任务策略:DiscardOldestPolicy,将队列中存在最久的任务给丢弃
➢ 自产自销策略:CallerRunsPolicy,那个线程提交任务就由那个线程负责运行
示例代码
package com.xt.blog.web.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 功能描述:自定义线程池
*
* @author wdl
* @date 2022/12/31 - 18:50
*/
@Configuration
public class ThreadPoolConfig {
@Bean("taskExecutor")
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
// 设置核心线程数
executor.setCorePoolSize(5);
// 设置最大线程数
executor.setMaxPoolSize(20);
//配置队列大小
executor.setQueueCapacity(20);
// 设置线程空闲时间,当超过核心线程之外的线程在空闲到达之后会被销毁(秒)
executor.setKeepAliveSeconds(60);
// 设置默认线程名称
executor.setThreadNamePrefix("ThreadExecutor");
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
//拒绝策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
/**
* 数据看板初始化
*
* @param userId
* @return
*/
@GetMapping("/initialization")
public Result<InitializationDataBoardVo> initializationDataBoard(@RequestParam("userId") Long userId
, @RequestParam("selectYear") String selectYear) throws InterruptedException {
// 初始化对象
InitializationDataBoardVo initializationDataBoardVo = new InitializationDataBoardVo();
// 创建线程计数器
final CountDownLatch countDownLatch = new CountDownLatch(3);
// 线程执行调用,获取用户信息
taskExecutor.execute(dataBoardService.getUserInfo(countDownLatch, initializationDataBoardVo, userId));
// 线程执行调用,数据看板界面月度注册的用户排行情况
taskExecutor.execute(dataBoardService.getUserRecordData(countDownLatch, initializationDataBoardVo, selectYear));
// 线程执行调用,数据看板用户数据统计
taskExecutor.execute(dataBoardService.getCountData(countDownLatch, initializationDataBoardVo));
// 线程计数器等待全部线程执行完成
countDownLatch.await();
return Result.success(initializationDataBoardVo);
}
@Override
public Runnable getUserInfo(CountDownLatch countDownLatch, InitializationDataBoardVo initializationDataBoardVo, Long userId) {
Runnable runnable = () -> {
try {
// feign远程调用到user微服务获取用户信息
Result<UserInfoVo> userInfoVoResult = userFeignService.getUserInfoByUserId(userId);
initializationDataBoardVo.setUserInfoVo(userInfoVoResult.getData());
} catch (Exception e) {
log.error("get userInfo error message:{}", e.getMessage());
} finally {
// 线程计数器减一
countDownLatch.countDown();
}
};
return runnable;
}
ThreadLocal
ThreadLocal是线程本地变量类,在多线程并执行过程中,将变量存储在ThreadLocal中,每个线程中都有独立的变量,因 此不会出现线程安全问题。
举例:
➢ 解决线程安全问题:每个线程绑定一个数据库连接,避免多个线程访问同一个数据库连接:SqlSession
➢ 跨函数参数传递:同一个线程,跨类,跨方法传递参数时可以使用ThreadLocal,每个线程绑定一个Token/Session
ThreadLocal底层原理
JDK1.8之前:ThreadLocal是Map所有线程拥有同一个,Key为thread,Value为具体值
JDK1.8:ThreadLocal依旧是Map,但一个线程一个ThreadLocalMap,key为ThreadLocal,Value为具体值
Future与FutureTask
Future主要方法
➢ get():方法返回结果取决于Callable任务执行的状态,任务有五种状态
① 正常完成:get立刻返回结果
② 尚未完成:还没开始或进行中的状态,get将阻塞直到任务完成
③ 抛出异常:get会抛出ExecutionException
④ 被取消: get会抛出CancellationException
⑤ 超时:设置超时时间,时间到了还没结果,会抛出TimeoutException
➢ get(timeout,TimeUnit):设置任务完成时间,没到则抛异常
➢ cancel():取消任务时,有三种情况
① 如果这个任务还没开始,任务会被取消,返回true
② 如果任务已经完成或已取消,返回false
③ 如果任务已经开始,则方法不会直接取消任务,而会判断是否可以取消,如果可以才会发出中断信号
➢ isDone() :判断是否执行完成
➢ isCancelled() : 判断是否被取消
Future与FutureTask案例
Future用法01-用线程池submit方法提交任务,返回值Future任务结果
➢ 用线程池提交任务,线程池会立即返回一个空的Future容器
➢ 当线程的任务执行完成,线程池会将该任务执行结果填入Future中
➢ 此时就可以从Future获取执行结果
Future用法02-用FutureTask来封装任务,获取Future任务的结果
➢ 用FutureTask包装任务,FutureTask是Future和Runnable接口的实现类
➢ 可以使用new Thread().start()或线程池执行FutureTask
➢ 任务执行完成,可以从FutureTask中获取执行结果