携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第2天,点击查看活动详情
前言
大家好,我是希留。
在有些业务场景中,系统对于响应时间有一定的要求,而一个方法里面同步执行的业务逻辑太多势必会影响响应速度,带来不好的用户体验。比如登录时记录登录用户的访问记录、注册时发送邮件、短信通知等等场景,不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。
异步任务可以通过多线程也可以通过消息队列来实现,目的都是为了实现不同业务之间的解耦,提高业务系统的响应速度。但是相对于小型系统采用多线程的方式相对更便捷,所以,这篇文章就记录一下我是如何使用多线程实现异步任务管理器来记录访问日志的。
一、异步任务管理器是什么?
顾名思义,就是用来对异步任务进行统一的管理,并提供了一种访问其唯一对象的方式,这样做的好处就是,在内存中有且仅有一个实例,减少了内存的开销,尤其对于频繁的创建和销毁实例,用这种方式来频繁执行多个异步任务性能是相对比较好的。
二、实现步骤
1.自定义线程池
执行异步任务时,需要将执行的任务放入到线程池中,所以需配置好我们的线程池。并创建一个调度线程池执行器,用来执行异步任务。代码如下(示例):
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程池大小
**/
private int corePoolSize = 50;
/**
* 最大可创建的线程数
**/
private int maxPoolSize = 200;
/**
* 队列最大长度
**/
private int queueCapacity = 1000;
/**
* 线程池维护线程所允许的空闲时间
**/
private int keepAliveSeconds = 300;
@Bean(name = "threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
return executor;
}
/**
* 执行周期性或定时任务
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
return new ScheduledThreadPoolExecutor(corePoolSize, new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build(),
new ThreadPoolExecutor.CallerRunsPolicy()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
// 打印线程异常信息
ThreadsUtils.printException(r, t);
}
};
}
/**
* 打印线程异常信息
*/
public static void printException(Runnable r, Throwable t) {
if (t == null && r instanceof Future<?>) {
try {
Future<?> future = (Future<?>) r;
if (future.isDone()) {
future.get();
}
} catch (CancellationException ce) {
t = ce;
} catch (ExecutionException ee) {
t = ee.getCause();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (t != null) {
logger.error(t.getMessage(), t);
}
}
2. 新建异步任务管理器类
代码如下(示例):
public class AsyncManager {
/**
* 操作延迟10毫秒
*/
private final int OPERATE_DELAY_TIME = 10;
/**
* 异步操作任务调度线程池
*/
private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");
/**
* 饿汉式单例模式
*/
private AsyncManager(){}
private static AsyncManager me = new AsyncManager();
public static AsyncManager me() {
return me;
}
/**
* 执行任务
* @param task 任务
*/
public void execute(TimerTask task) {
executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
3. 新建异步工厂类
设计这个类主要是用来产生 TimerTask 的,代码如下(示例):
@Slf4j
public class AsyncFactory {
/**
* 记录登录信息
* @param username 用户名
* @param status 状态
* @param message 消息
* @param args 列表
* @return 任务task
*/
public static TimerTask recordLoginLog(final String username, final String status, final String message,final Object... args) {
// 客户端操作系统、浏览器等信息
final UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
// 请求的IP地址
final String ip = ServletUtil.getClientIP(ServletUtils.getRequest());
return new TimerTask() {
@Override
public void run() {
String address = AddressUtils.getRealAddressByIp(ip);
// 获取客户端操作系统
String os = userAgent.getOs().getName();
// 获取客户端浏览器
String browser = userAgent.getBrowser().getName();
// 封装对象
XlLoginLog loginLog = new XlLoginLog();
loginLog.setUserCode(username);
loginLog.setIpaddr(ip);
loginLog.setLoginLocation(address);
loginLog.setBrowser(browser);
loginLog.setOs(os);
loginLog.setMsg(message);
loginLog.setLoginTime(new Date());
// 日志状态
if (Constants.LOGIN_FAIL.equals(status)) {
loginLog.setStatus(Integer.valueOf(Constants.FAIL));
} else {
loginLog.setStatus(Integer.valueOf(Constants.SUCCESS));
}
// 插入数据
SpringUtils.getBean(IXlLoginLogService.class).create(loginLog);
}
};
}
}
4. 调用
例如:在登录的方法中链式调用,与同步方式不同,开发者不用考虑当进行登录操作是否进行日志操作,在异步的方式中,业务的操作与日志的操作分开来。执行流程:AsyncManager.me()获取一个AsyncManager对象,执行execute方法,执行任务,传入的是一个task对象。实现了Runnable接口,是一个任务,由线程Thread去执行。
recordLoginLog方法返回的是TimerTask定时任务类,将用户登录信息记录到日志中作为一个定时任务,交给定时任务调度线程池scheduledExecutorService,scheduledExecutorService通过在异步任务管理器类中,用getBean()从IOC容器中获取。
5. 实现效果
进行登录操作时,会异步进行日志的记录。
总结
好了,以上就是这篇文章的全部内容了,感谢大家的阅读。
若觉得本文对您有帮助的话,还不忘点赞评论关注,支持一波哟~