使用线程池结合短信发送服务
1.1使用线程池的理由
我们为什么要使用线程池?
- 降低线程创建和销毁的损耗
- 提高系统的响应速度,当有任务到达的时候,通过复用存在的线程,无需等待新的线程的创建就能立即执行
- 控制线程数量,控制并发数,如果线程无休止创建,可能会导致内存占用过多产生OOM,且会造成cpu的过度切换(cpu的切换有时间成本)
- 也可以创建延时定时线程池
1.2线程池使用队列的原因
控制线程无休止的创建,导致内存占用过多造成oom,且导致cpu过度切换
由于线程池创建线程需要获取mainlock这个锁,影响并发,阻塞队列可以做缓冲
1.3线程池为什么使用阻塞队列而不是非阻塞队列
阻塞队列可以让队列为空的时候,获取任务的的线程阻塞,让这个线程进入wait状态,获取cpu资源,当任务中有队列的时候唤醒该线程聪队列中取出消息执行。
让线程不至于一直占用cpu的资源,例如:
while (task != null || (task = getTask()) != null) {
do something;
}
不用阻塞队列也行,但是比较麻烦
1.4如何配置线程池?
-
cpu密集型。
例如:计算量大。尽量使用较小线程池,一般是cpu的核心数+1。cpu密集型让cpu的使用率高,如果线程数目开多了,就会过度切换,得不偿失
-
IO密集型。
读写较多。可以使用稍大线程池,一般是2*CPU核心数。因为一般IO密集型的cpu使用率较低,可以让cpu在等待io的时候去做一些其它事情,从而充分利用cpu的时间。
-
混合型任务。尽量做任务的拆分,换成前两个,做处理
1.5Sping线程池
Spring利用任务执行器TaskExecutor来实现多线程并发编程。使用ThreadPoolTaskExecutor实现一个基于线程池的TaskExecutor,
我们在配置类需要用 @EnableAsync开启异步,并且在异步方法哪里用@Async声明异步任务。
SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,每次调用都会创建一个新的线程。
SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方
ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类
SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类
ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装
1.6.@Async的事务调度处理
我们可以给大方法加上@Async, 大方法里面调用 A/B两个方法,在这两个方法里面做细粒度的@Transactional,或者用编程式事务。
1.7.配置类的方法说明
Spring 中的ThreadPoolExecutor是借助JDK并发包中的java.util.concurrent.ThreadPoolExecutor来实现的。其中一些值的含义如下:
- int corePoolSize:线程池维护线程的最小数量
- int maximumPoolSize:线程池维护线程的最大数量,线程池中允许的最大线程数,线程池中的当前线程数目不会超过该值。如果队列中任务已满,并且当前线程个数小于maximumPoolSize,那么会创建新的线程来执行任务。
- long keepAliveTime:空闲线程的存活时间TimeUnit
- unit:时间单位,现由纳秒,微秒,毫秒,秒
- BlockingQueue workQueue:持有等待执行的任务队列,一个阻塞队列,用来存储等待执行的任务,当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待
- RejectedExecutionHandler handler 线程池的拒绝策略,是指当任务添加到线程池中被拒绝,而采取的处理措施。
- 当任务添加到线程池中之所以被拒绝,可能是由于:第一,线程池异常关闭。第二,任务数量超过线程池的最大限制。
- Reject策略预定义有四种:
- ThreadPoolExecutor.AbortPolicy策略,是默认的策略,处理程序遭到拒绝将抛出运行时 RejectedExecutionException
- ThreadPoolExecutor.CallerRunsPolicy策略 ,调用者的线程会执行该任务,如果执行器已关闭,则丢弃.
- ThreadPoolExecutor.DiscardPolicy策略,不能执行的任务将被丢弃.
- ThreadPoolExecutor.DiscardOldestPolicy策略,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)
- 自定义策略:当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务
短信服务实战
- 做好异步线程池的配置。由于短信发送是IO密集型,所以设置为2*cpu核数
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean("smsMessage")
public Executor sendSmsMessageAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(4);
executor.setMaxPoolSize(4);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(60);
executor.setThreadNamePrefix("sendSmsMessage-");
executor.initialize();
return executor;
}
}
2.异步方法
@Override
@Async("smsMessage")
public void sendVerifyCodeMessage(String phoneNum) {
try{
Client client = AliyunUtil.createClient(accessKeyId, accessKeySecret);
String saveVerifyCode = verifyCodeService.saveVerifyCode(phoneNum);
SendSmsRequest sendSmsRequest = new SendSmsRequest()
.setPhoneNumbers(phoneNum)
.setSignName(verifyCodeSignName)
.setTemplateCode(verifyCodeMessageTemplateId)
.setTemplateParam("{'code':'" + saveVerifyCode +"'}")
.setPhoneNumbers(phoneNum);
// 复制代码运行请自行打印 API 的返回值
SendSmsResponse sendSmsResponse = client.sendSms(sendSmsRequest);
// return saveVerifyCode;
} catch (Exception e) {
logger.error("发送失败", e);
// return "";
}
}