1.我们都知道spring只是为我们简单的处理线程池,每次用到线程总会new 一个新的线程,效率不高,所以我们需要自定义一个线程池。
2.自定义线程池有两种方法,第一种自定义线程池然后使用自己的自定义的,第二种重写spring默认的线程池,然后使用自己重写过的线程池
一:重写spring默认的线程池
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.util.ObjectUtils;
import org.springframework.util.concurrent.ListenableFuture;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.lang.management.OperatingSystemMXBean;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author yzd
* @Description: 显示线程池信息
* <p>
* 显示线程执行情况:任务总数、已完成数、活跃线程数,队列大小信息
* </p>
* @create 2021-04-29 9:28
**/
@Primary
public class VisibleThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private void showThreadPoolInfo(String prefix) {
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor();
if (ObjectUtils.isEmpty(threadPoolExecutor)) {
return;
}
log.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]",
this.getThreadNamePrefix(),
prefix,
threadPoolExecutor.getTaskCount(),
threadPoolExecutor.getCompletedTaskCount(),
threadPoolExecutor.getActiveCount(),
threadPoolExecutor.getQueue().size());
this.showMemoryInfo();
}
/**
* execute 没有返回值 异步
* submit有返回值,所以需要返回值的时候必须使用submit
* 如果不对返回值Future调用get()方法,都会吃掉异常
*
* @param task task
*/
@Override
public void execute(Runnable task) {
showThreadPoolInfo("1. do execute");
super.execute(task);
}
@Override
public void execute(Runnable task, long startTimeout) {
showThreadPoolInfo("2. do execute timeout");
super.execute(task, startTimeout);
}
@Override
public Future<?> submit(Runnable task) {
showThreadPoolInfo("1. do submit");
return super.submit(task);
}
@Override
public <T> Future<T> submit(Callable<T> task) {
showThreadPoolInfo("2. do submit");
return super.submit(task);
}
@Override
public ListenableFuture<?> submitListenable(Runnable task) {
showThreadPoolInfo("1. do submitListenable");
return super.submitListenable(task);
}
@Override
public <T> ListenableFuture<T> submitListenable(Callable<T> task) {
showThreadPoolInfo("2. do submitListenable");
return super.submitListenable(task);
}
/**
* Java 虚拟机的内存系统
* <p>
* init约等于xms的值,max约等于xmx的值
* used是已经被使用的内存大小,committed是当前可使用的内存大小(包括已使用的)
* committed >= used。committed不足时jvm向系统申请,若超过max则发生OutOfMemoryError错误
* 机器内存小于192M,堆内存最大为二分之一
* 机器内存大于等于1024M,堆内存最大为四分之一
* JVM初始分配的内存由-Xms指定,默认是物理内存的1/64;
* JVM最大分配的内存由-Xmx指定,默认是物理内存的1/4。
* 默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
* 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
* 因此服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小。
* </p>
*/
private void showMemoryInfo() {
MemoryMXBean mem = ManagementFactory.getMemoryMXBean();
MemoryUsage heap = mem.getHeapMemoryUsage();
log.info("Heap committed:" + heap.getCommitted() / 1024 / 1024
+ "MB, init:" + heap.getInit() / 1024 / 1024
+ "MB, max:" + heap.getMax() / 1024 / 1024
+ "MB, used:" + heap.getUsed() / 1024 / 1024 + "MB");
}
/**
* Java 虚拟机在其上运行的操作系统
*/
private void showSystem() {
OperatingSystemMXBean op = ManagementFactory.getOperatingSystemMXBean();
log.info("架构:[{}],处理器数:[{}],操作系统:[{}],系统版本:[{}]", op.getArch(), op.getAvailableProcessors(), op.getName(), op.getVersion());
}
}
二:自定义线程池配置类
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import javax.annotation.PreDestroy;
import java.util.concurrent.ThreadPoolExecutor;
/**
* ExecutorConfig
*
* @author : YZD
* @date : 2023/3/7 0007 21:18
*/
@Configuration
@EnableAsync
@Slf4j
@EnableScheduling
public class ExecutorConfig implements SchedulingConfigurer {
private static final ThreadPoolTaskExecutor EXECUTOR = new VisibleThreadPoolTaskExecutor();
/**
* 功能描述: 线程池
*
* @return : org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor
* @author : yzd e-mail: 121665820@qq.com
* @create : 2023/2/11 16:04
*/
@Bean
@Primary
public ThreadPoolTaskExecutor asyncServiceExecutor() {
log.info("start asyncService Executor");
//使用visibleThreadPoolTaskExecutor
// 通过Runtime方法来获取当前服务器cpu内核,根据cpu内核来创建核心线程数和最大线程数
int availableProcessors = Runtime.getRuntime().availableProcessors();
/**
* 配置线程个数
如果是CPU密集型任务,那么线程池的线程个数应该尽量少一些,一般为CPU的个数+1条线程(大量计算)
如果是IO密集型任务,那么线程池的线程可以放的很大,如2*CPU的个数(IO操作)
*/
EXECUTOR.setCorePoolSize(availableProcessors + 1);
// 允许线程池超时
EXECUTOR.setAllowCoreThreadTimeOut(true);
//配置最大线程数
EXECUTOR.setMaxPoolSize(availableProcessors * 4);
// 空闲存活时间
EXECUTOR.setKeepAliveSeconds(60);
// 设置 等待终止秒数
EXECUTOR.setAwaitTerminationSeconds(60);
//配置队列大小
EXECUTOR.setQueueCapacity(availableProcessors * 100);
//配置线程池中的线程的名称前缀
EXECUTOR.setThreadNamePrefix("async-thread-pool-");
// 等待所有任务结束再关闭线程池
EXECUTOR.setWaitForTasksToCompleteOnShutdown(true);
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
EXECUTOR.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
EXECUTOR.initialize();
return EXECUTOR;
}
/**
* 创建一个定长线程池,支持定时及周期性任务执行
*/
@Bean
public ThreadPoolTaskScheduler scheduledThreadPoolExecutor() {
log.info("start Scheduled Executor");
ThreadPoolTaskScheduler executor = new ThreadPoolTaskScheduler();
// 大于等于 任务数量
executor.setPoolSize(10);
executor.setThreadNamePrefix("scheduled-thread");
// 等待时长
executor.setAwaitTerminationSeconds(60);
// 调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置饱和策略
//CallerRunsPolicy:线程池的饱和策略之一,当线程池使用饱和后,直接使用调用者所在的线程来执行任务;如果执行程序已关闭,则会丢弃该任务
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
/**
* 功能描述: 配置 @Scheduled 定时器所使用的线程池
* 配置任务注册器:ScheduledTaskRegistrar 的任务调度器
*
* @param scheduledTaskRegistrar scheduledTaskRegistrar
* @return : void
* "@Scheduled" 默认是单线程执行的,所以在需要的时候,我们可以设置一个线程池去执行定时任务。
* @author : yzd e-mail: 121665820@qq.com
* @create : 2023/2/11 16:30
*/
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
//可配置两种类型:TaskScheduler、ScheduledExecutorService
// scheduledTaskRegistrar.setScheduler(scheduledThreadPoolExecutor());
//只可配置一种类型:taskScheduler
scheduledTaskRegistrar.setTaskScheduler(scheduledThreadPoolExecutor());
}
/**
* 优雅关闭线程池
*/
@PreDestroy
private void shutdownGracefully() {
log.error("ThreadPool close");
}
}
三:使用多线程
@Slf4j
@Component
public class LogAspect {
@Autowired
private ThreadPoolTaskExecutor asyncServiceExecutor;
private void controllerAspect() {
// 异步 CompletableFuture
asyncServiceExecutor.execute(() -> {
// 日志存储
);
}
}
四、使用定时任务
import org.springblade.modules.article.service.IArticleService;
import org.springblade.modules.im.utils.MsgUtils;
import org.springblade.modules.monitor.service.IMonitorLifeSignService;
import org.springblade.modules.order.req.IServiceItemInfoService;
import org.springblade.modules.plan.service.IPlanNoticeService;
import org.springblade.modules.plan.service.IPlanService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @Title: StartInit
* @author: yzd e-mail: 121665820@qq.com
* @date: 2023/2/13 17:22
* @ClassName: StartInit
* @Description: 项目初始化完毕后, 调用方法,提供服务
* 采用 定时 线程池 , 执行 定时 任务
* 每个 任务 把 执行 时间 错开 , 以免 多任务 执行 失败
* scheduleAtFixedRate,指定间隔时间执行一次任务,间隔时间为前一次执行开始到下次任务开始时间
* scheduleWithFixedDelay,指定间隔时间执行一次任务,间隔时间为前一次任务完成到下一次开始时间
* schedule(Runnable task, Date stateTime),在指定时间执行一次定时任务
* schedule(Runnable task, Trigger trigger),动态创建指定表达式cron的定时任务
*/
@Component
public class StartTaskConfig implements CommandLineRunner {
@Autowired
private ThreadPoolTaskScheduler threadPoolTaskScheduler;
@Autowired
private IPlanService planService;
/**
* 功能描述: 定时任务1
* 扫描所有的 Channel,关闭失效的Channel, 每 24 小时
*
* @return : void
* @author : yzd e-mail: 121665820@qq.com
* @create : 2023/3/16 14:33
*/
@Scheduled(fixedDelay = 24 * 60 * 60 * 1000L, initialDelay = 60 * 60 * 1000L)
public void scanNotActiveChannel() {
MsgUtils.scanNotActiveChannel();
}
@Override
public void run(String... args) {
// 定时任务2: 方案 自启动, 每 65 分钟 执行
threadPoolTaskScheduler.scheduleWithFixedDelay(planService::autoStartPlan, 65 * 60 * 1000L);
}