上篇记录了如何自定义线程池,这篇主要讲讲业务场景应用。假设在一个用户异常登录后,我们需要给用户绑定的邮箱发送消息提醒,这个时候就可以把这发送邮箱的任务提交给线程池去执行。但是当某个时间段,异常登录的用户特别多,导致线程池处理不过来任务时,就会执行拒绝策略。那此时就可以自定义拒绝策略,可以把线程池拒绝的任务先保存,再用定时任务去扫描。大概的过程如图:
当然有人可能会说,这是多此一举,直接用消息队列不就解决了。这里只是提供一种思路,实现业务的方法千千万。假如当前应用没有引入消息队列,为了这个小功能再去用队列,也不划算,个人见解,勿喷。
上代码:
1.依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>threadpool-redis</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>
</dependencies>
</project>
2.配置
spring:
redis:
host: localhost
port: 6379
password: 123456
3.线程池配置
@Configuration
public class ThreadPoolConfig {
@Autowired
private RedisTemplate redisTemplate;
/**
* 将线程池注入到容器,交给spring管理
* @return
* 最大线程数2 ,队列长度为1,那么就可以向线程池最多提交3个任务,第四个的时候会执行拒绝策略
*/
@Bean
public ThreadPoolExecutor creatPool(){
return new ThreadPoolExecutor(1,2,60,
TimeUnit.SECONDS,new ArrayBlockingQueue<>(1),
Executors.defaultThreadFactory(),new MyRejectedExecutionHandler(redisTemplate));
}
}
4.自定义拒绝策略
public class MyRejectedExecutionHandler implements RejectedExecutionHandler {
//这个地方不能注入
private RedisTemplate redisTemplate;
final private String THREAD_TASK_KEY = "threadPoolTask";
/**
* 当线程池最大线程数量达到最大时,且有新任务提交就会执行拒绝策略
* @param r 被拒绝的任务
* @param executor 当前线程池
*/
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
//控制台打印代替日志
System.out.println("执行自定义拒绝策略,提交任务");
//redis存储对象必须要实现序列化接口,但是Runnable是jdk接口,所以这里是使用阿里的fastjosn来实现序列化
String s = JSON.toJSONString(r);
//从左进一个任务
redisTemplate.opsForList().leftPush(THREAD_TASK_KEY,s);
}
public MyRejectedExecutionHandler(RedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
}
5.定时任务
@Configuration
@EnableScheduling //开启定时任务
public class ThreadPoolTaskJob {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
final private String THREAD_TASK_KEY = "threadPoolTask";
//每隔5秒执行一次
@Scheduled(cron = "0/5 * * * * ?")
public void configureTasks() {
//从右边弹出一个任务,强制转化
String s = (String) redisTemplate.opsForList().rightPop(THREAD_TASK_KEY);
Runnable runnable = JSON.parseObject(s, Runnable.class);
if(runnable != null){
System.out.println("定时任务执行,扫描到了任务,重新提交线程池");
threadPoolExecutor.submit(runnable);
}else {
System.out.println("定时任务执行,任务队列已空");
}
}
}
6.启动类中提交任务,也可以利用网络请求来提交任务
@SpringBootApplication
public class App {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(App.class, args);
//从容器中获取线程池对象
ThreadPoolExecutor threadPoolExecutor = run.getBean(ThreadPoolExecutor.class);
for (int i = 0; i < 4; i++) {
//提交任务
threadPoolExecutor.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"->正在执行");
try {
//休眠10秒,模拟线程处理业务
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
}
}