线程在项目中的应用
1. 发短信
发短信的场景有很多,比如手机号+验证码登录注册,电影票买完之后会发送取票码,发货之后会有物流信息,支付之后银行发的付款信息,电力系统的电费预警信息等等。
在这些业务场景中,有一个特征,主业务和短信业务可以割裂。
比如手机号+验证码登录,当我们点击获取验证码的时候,会连接短信业务平台发短信,但是发短信这个业务受短信平台的影响,可能会有一定的延时,我们不一定非要等短信平台返回之后,在给用户返回。
我们可以先返回获取验证码成功,将发短信的业务放入另一个线程中执行,用户晚一会收到短信对整体的业务流程也不会受到影响,反而提升了用户的体验。
1.1 代码演示
package com.yf.sso.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
@Slf4j
public class ExecutorConfig {
@Bean
public Executor asyncServiceExecutor() {
log.info("start asyncServiceExecutor");
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//配置核心线程数
executor.setCorePoolSize(5);
//配置最大线程数
executor.setMaxPoolSize(5);
//配置队列大小
executor.setQueueCapacity(5);
//配置线程池中的线程的名称前缀
executor.setThreadNamePrefix("async-service-");
// rejection-policy:当pool已经达到max size的时候,如何处理新任务
// CALLER_RUNS:不在新线程中执行任务,而是有调用者所在的线程来执行
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//执行初始化
executor.initialize();
return executor;
}
}
@Autowired
private ThreadService threadService;
public boolean sendSms(String phone) {
/**
* 1. 调用短信平台 发送短信 如果发送成功,将验证码存入redis,redis要有过期时间
* 2. 发送成功,返回成功
*/
//短信验证码 要生成
int code = RandomUtils.nextInt(100000, 999999);
log.info("短信验证码:{}",code);
//放入线程池执行,不影响当前的业务,立马返回
threadService.sendSMS(phone,code);
return true;
}
package com.yf.sso.service;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Service
@Slf4j
public class ThreadService {
@Autowired
private SmsService smsService;
@Autowired
private RedisTemplate<String,String> redisTemplate;
@Async("asyncServiceExecutor")
public void sendSMS(String phone , int code) {
boolean isSend = smsService.send(phone,code);
if (isSend){
redisTemplate.opsForValue().set("LOGIN_"+phone,String.valueOf(code), Duration.ofMinutes(time));
}
}
}
2. 推送
比如有一个业务场景:
有一个审核业务,当收到数据后,需要将这些数据发送给第三方的监管系统进行审核。
数据量级有百万之多,一条数据推送按一秒计算,那么需要过百万秒,200多个小时以上。
解决:
考虑引入多线程进行并发操作,降低数据推送时间,提供数据推送的实时性。
要注意的问题:
-
防止重复推送
可以考虑将数据切分成不同的数据段,每个线程负责一个
-
失败处理
推送失败后,进行失败推送的数据记录,用额外的程序处理失败数据
2.1 代码演示
package com.yf.push.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
@Service
public class PushService {
@Autowired
private ThreadService threadService;
public void oldPush(){
int dataNum = 10000;
int[] array = new int[dataNum];
for (int i = 0;i<dataNum;i++){
array[i] = i;
}
long start = System.currentTimeMillis();
//推送的数据数量
for (int i = 0 ; i < array.length;i++){
//推送到第三方审核平台
pushSend(array[i]);
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
@Autowired
private ThreadPoolTaskExecutor asyncServiceExecutor;
public void pushNew(){
int dataNum = 10000;
int[] array = new int[dataNum];
for (int i = 0;i<dataNum;i++){
array[i] = i;
}
long start = System.currentTimeMillis();
//推送的数据数量
//假设线程池数量为10,也就是说 每个线程处理 1000条数据 共需要10次循环
for (int i = 0 ; i < 10;i++){
int s = i * 1000;
int e = i * 1000 + 1000 - 1;
//推送到第三方审核平台
//这个是假设 有10000条数据,那么每次推送处理1000条数据
threadService.push(array,s,e);
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
public void pushNewCall(){
int dataNum = 10000;
int[] array = new int[dataNum];
for (int i = 0;i<dataNum;i++){
array[i] = i;
}
long start = System.currentTimeMillis();
//推送的数据数量
//假设线程池数量为10,也就是说 每个线程处理 1000条数据 共需要10次循环
List<Future> futureList = new ArrayList<>();
for (int i = 0 ; i < 10;i++){
int s = i * 1000;
int e = i * 1000 + 1000 - 1;
//推送到第三方审核平台
//这个是假设 有10000条数据,那么每次推送处理1000条数据
Future<Integer> submit = asyncServiceExecutor.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return threadService.push1(array, s, e);
}
});
futureList.add(submit);
}
for (Future future : futureList) {
try {
System.out.println("本轮线程执行数量:" +future.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
System.out.println((end - start) + "ms");
}
private void pushSend(int data) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.yf.push.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class ThreadService {
@Async("asyncServiceExecutor")
public void push(int[] array, int start, int end){
long s = System.currentTimeMillis();
for (int i = start;i<=end;i++){
pushSend(array[i]);
//推送失败 可以记录日志
}
long e = System.currentTimeMillis();
System.out.println((e-s)+"ms");
}
public int push1(int[] array, int start, int end){
int count = 0;
long s = System.currentTimeMillis();
for (int i = start;i<=end;i++){
count++;
pushSend(array[i]);
//推送失败 可以记录日志
}
long e = System.currentTimeMillis();
System.out.println((e-s)+"ms");
return count;
}
public void pushSend(int dataNum){
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
测试:
package com.yf.push.service;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.TimeUnit;
@SpringBootTest
public class TestPushService {
@Autowired
private PushService pushService;
@Test
public void testOldPush(){
pushService.oldPush();
}
@Test
public void testNewPush(){
pushService.pushNew();
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Test
public void testNewPushCall(){
pushService.pushNewCall();
try {
TimeUnit.HOURS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}