序
今天跟添加分享一个多线程与自动任务谈恋爱的悲惨故事,他们的结合,如果造化弄人,搞不好就是同归于尽。
一、相识
一次产品设计会上,促使了2个人”多线程“、”自动任务“的结合,本来是计划一起配合用手段调度资源造就稳定、丝滑、流畅的时代。可是造物主要是控制不好,就会是一场所有的所有陪葬的悲剧。
二、结合示例
1.多线程集成
多线程集成是前提,这里就不重复说了,见往期的博文。(下面整体代码示例也有说明)
2.自动任务集成
这里实现方式很多,也不多说,一般用springBoot的Scheduled就够用了,不需要额外引包。
三、整体代码示例
这里是我个人的一个小项目,不涉密,可以完全分享,下面就从多线程、自动任务整个整合分享一遍。
1.多线程配置项
#多线程配置
thread:
config:
corePoolSize: 5
maxPoolSize: 15
queueCapacity: 25
这里是减配的,需要详细配置、细粒度优化的,可以自行发挥。当然下面的配置类也需要优化增加初始设置。
2.多线程配置类
import com.xiaotian.datadiver.exception.MyRejectedExecutionHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
* 多线程配置类
*
* @author zhengwen
*/
@Configuration
@EnableAsync
@Slf4j
public class ThreadConfig implements AsyncConfigurer {
/**
* 核心线程数
*/
@Value("${thread.config.corePoolSize:5}")
private Integer corePoolSize;
/**
* 最大线程数量
*/
@Value("${thread.config.maxPoolSize:15}")
private Integer maxPoolSize;
/**
* 线程处理队列长度
*/
@Value("${thread.config.queueCapacity:25}")
private Integer queueCapacity;
/**
* 线程池维护线程所允许的空闲时间
*/
@Value("${thread.config.keepAliveSeconds:300}")
private Integer keepAliveSeconds;
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
log.info("---------[多线程配置初始化],核心线程数:{},最大线程数量:{},线程处理队列长度:{}", corePoolSize, maxPoolSize,
queueCapacity);
// 核心线程数
executor.setCorePoolSize(corePoolSize);
// 最大线程数量
executor.setMaxPoolSize(maxPoolSize);
// 线程处理队列长度
executor.setQueueCapacity(queueCapacity);
// 线程空闲时间
executor.setKeepAliveSeconds(keepAliveSeconds);
//当调度器shutdown被调用时等待当前被调度的任务完成
executor.setWaitForTasksToCompleteOnShutdown(false);
//由调用线程处理该任务
//executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//自定义线程池饱和机制
executor.setRejectedExecutionHandler(new MyRejectedExecutionHandler());
executor.initialize();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
log.info("-----------多线程异常handler------------");
return (throwable, method, objects) -> {
log.error("Asyn返回异常:", throwable);
if (method != null && method.getName() != null) {
log.error("Asyn返回异常方法:{}", method.getName());
}
};
}
}
3.自动任务配置项
#自带schedule自动任务调度开关配置
scheduling:
enabled: true
business:
licenseCheck:
cron: 0 50 8 * * ?
masterSlaveRun:
fixedDelay: 3000
这其实就是一个开关,然后是2个自定义的业务配置cron表达式。
4.自动任务配置业务类
import cn.hutool.core.date.DateUtil;
import com.xiaotian.datadiver.service.ThreadService;
import com.xiaotian.datadiver.util.LicenseUtil;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
/**
* 业务自动任务
*
* @author zhengwen
**/
@Configuration
@EnableScheduling
@ConditionalOnProperty(prefix = "scheduling", name = "enabled", havingValue = "true")
@Slf4j
public class BusinessTask {
@Resource
private ThreadService threadService;
/**
* 校验授权签名
*/
@Scheduled(cron = "${scheduling.business.licenseCheck.cron}")
public void licenseCheck() {
log.info("--校验授权签名校验--");
boolean licensePass = LicenseUtil.checkSoftLicense();
if (licensePass) {
log.info("--签名校验通过--");
} else {
log.info("--签名校验不通过,请联系管理员--");
//关闭服务
Runtime.getRuntime().exit(500);
}
}
/**
* 多线程-主从线程与周期测试
*/
@Scheduled(fixedDelayString = "${scheduling.business.masterSlaveRun.fixedDelay}")
public void masterSlaveThreadRunTest() {
log.info("--多线程-主从线程与周期测试--");
String now = DateUtil.now();
//执行完成后间隔3秒,设置睡5秒钟
long sleepTime = 5000;
threadService.slaveThreadRun(now, sleepTime);
log.info("--主线程运行完成,时间:{}", now);
}
}
注意这个类上的注解,分别是配置扫描注解、开启自动任务注解、配置文件读取注解。
5.service类
import java.util.concurrent.Future;
/**
* @author zhengwen
**/
public interface ThreadService {
/**
* 从线程方法
* @param now
* @param sleepTime
*/
void slaveThreadRun(String now,long sleepTime);
/**
*
* @param now
* @param sleepTime
* @return
*/
Future<?> slaveThreadRunHasResult(String now,long sleepTime);
}
这里定义了2个接口,分别是
- 从线程业务方法
- 带返回值的从线程业务方法(这里先买个关子,用这个讲述他们的故事更明显一点,下期分享)
6.service实现类
import com.xiaotian.datadiver.service.ThreadService;
import java.util.concurrent.Future;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
/**
* @author zhengwen
**/
@Slf4j
@Service
public class ThreadServiceImpl implements ThreadService {
@Async
@Override
public void slaveThreadRun(String now, long sleepTime) {
//主线程是1分钟执行一次,这里我就让睡1分半钟
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
log.error("----从线程异常:{}", e.getMessage(), e);
}
log.info("我{}睡了{},醒了,可以继续了", now, sleepTime);
}
@Override
public Future<?> slaveThreadRunHasResult(String now, long sleepTime) {
return null;
}
}
注意实现方法上的@Async注解,否在开启线程无效
到此代码就分享完了。
四、运行结果
代码分析:
首先分清主从:
主线程:自动任务方法(masterSlaveThreadRunTest)
从线程:自动任务里调用的业务方法(slaveThreadRun)
结果分析:
图上已经标明了交叉打印,大家仔细想想我自动任务设置的是希望这样吗?
自动任务设置解释:
我设置的是希望自动任务执行一次完成后,间隔3s再进行下一次,对吧?
@Scheduled(fixedDelayString = 完成后延迟毫秒数)
需要详细理解Scheduled的8个配置,可以自行百度。
悲剧起源:
试想一下,我想的是一次执行完成后,间隔10s再执行,而业务方法一次执行完成需要执行60s,那将会是什么情况?
这里就是主线程间隔3s执行一次,而从线程执行一次需要5s。这里显然主线程不会认为从线程5s执行完了再过3s再执行下一次,如果是,那这里应该是顺序打印,对吧?
悲剧发生背景:
如果这2个时间相差巨大,随着时间的推移,你们说会发生啥?
悲剧结果猜想:
服务器的线程资源够他们这样组合调度吗?
总结
多线程固然好用,自动任务固然自动。
当他们决定在一起,一定要注意他们的”脚步差”,否在,渐行渐远,心生怨念,拉整个服务器陪葬。也许有人会说,不是加了自动任务的抛弃策略吗?可是要知道,多线程本就不应该一直被占满而等抛弃,那个时候服务器已经可能都忙不过来做抛弃了,何来稳定、丝滑、流畅?