文章目录
前言
今天来简单总结一下JAVA中的异步执行,那么有异步肯定就有同步,他们有什么区别呢?我们为什么要进行异步操作呢?
- 同步:在所有操作执行完毕后返回给客户端,在这个过程中需要在不断的等待,且客户端不能关闭。
- 异步:将本次请求放入消息队列,直接反馈给用户结果,然后由程序去按照队列顺序执行业务逻辑。
那么了解了同步与异步的区别之后,我们自然也就能明白为什么要进行异步操作了。
在某些比较特殊的场合下,我们需要及时给出响应,又不想因为业务逻辑的执行导致需要等待的时候,就需要用到我们今天的主角“异步”了。
异步的使用场景:
- 不涉及共享资源,或对共享资源只读,即非互斥操作
- 没有时序上的严格关系
- 不需要原子操作,或可以通过其他方式控制原子性
- 常用于IO操作等耗时操作,因为比较影响客户体验和使用性能
- 不影响主线程逻辑
异步的好处:
- 异步流程可以立即给调用方返回初步的结果。
- 异步流程可以延迟给调用方最终的结果数据,在此期间可以做更多额外的工作,例如结果记录等等。
- 异步流程在执行的过程中,可以释放占用的线程等资源,避免阻塞,等到结果产生再重新获取线程处理。
- 异步流程可以等多次调用的结果出来后,再统一返回一次结果集合,提高响应效率。
异步的使用场景及好处参考链接:同步、异步的使用场景及好处
在JAVA中异步的实现方式
一:实现异步工厂
项目中有看到某位大佬对异步、线程池进行了封装,简化了调用的操作,所以这里记录一下实现方式。
需要依赖的工具类
线程相关工具类,直接放进项目即可
/**
* 线程相关工具类
*
* @author LiWT
*/
@Slf4j
public class ThreadUtils {
/**
* sleep等待,单位为毫秒
*/
public static void sleep(long milliseconds) {
try {
Thread.sleep(milliseconds);
} catch (InterruptedException e) {
log.error("sleep error:", e);
}
}
/**
* 停止线程池
* 先使用shutdown, 停止接收新任务并尝试完成所有已存在任务.
* 如果超时, 则调用shutdownNow, 取消在workQueue中Pending的任务,并中断所有阻塞函数.
* 如果仍然超时,则强制退出.
* 另对在shutdown时线程本身被调用中断做了处理.
*/
public static void shutdownAndAwaitTermination(ExecutorService pool) {
if (pool != null && !pool.isShutdown()) {
pool.shutdown();
try {
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
pool.shutdownNow();
if (!pool.awaitTermination(120, TimeUnit.SECONDS)) {
log.info("Pool did not terminate");
}
}
} catch (InterruptedException ie) {
pool.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
/**
* 打印线程异常信息
*/
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) {
log.error(t.getMessage(), t);
}
}
}
Spring工具类 方便在非Spring管理环境中获取Bean
/**
* spring工具类 方便在非spring管理环境中获取bean
*
* @author LiWT
*/
@Component
public final class SpringUtils implements BeanFactoryPostProcessor {
/**
* Spring应用上下文环境
*/
private static ConfigurableListableBeanFactory beanFactory;
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
SpringUtils.beanFactory = beanFactory;
}
/**
* 获取对象
*
* @param name
* @return Object 一个以所给名字注册的bean的实例
* @throws BeansException
*/
@SuppressWarnings("unchecked")
public static <T> T getBean(String name) throws BeansException {
return (T) beanFactory.getBean(name);
}
/**
* 获取类型为requiredType的对象
*
* @param clz
* @return
* @throws BeansException
*/
public static <T> T getBean(Class<T> clz) throws BeansException {
T result = (T) beanFactory.getBean(clz);
return result;
}
/**
* 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
*
* @param name
* @return boolean
*/
public static boolean containsBean(String name) {
return beanFactory.containsBean(name);
}
/**
* 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
*
* @param name
* @return boolean
* @throws NoSuchBeanDefinitionException
*/
public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException {
return beanFactory.isSingleton(name);
}
/**
* @param name
* @return Class 注册对象的类型
* @throws NoSuchBeanDefinitionException
*/
public static Class<?> getType(String name) throws NoSuchBeanDefinitionException {
return beanFactory.getType(name);
}
/**
* 如果给定的bean名字在bean定义中有别名,则返回这些别名
*
* @param name
* @return
* @throws NoSuchBeanDefinitionException
*/
public static String[] getAliases(String name) throws NoSuchBeanDefinitionException {
return beanFactory.getAliases(name);
}
/**
* 获取aop代理对象
*
* @param invoker
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T getAopProxy(T invoker) {
return (T) AopContext.currentProxy();
}
}
线程池配置
/**
* 线程池配置
*
* @author LiWT
**/
@Configuration
public class ThreadPoolConfig {
/**
* 核心线程池大小
*/
private final int corePoolSize = 50;
/**
* 最大可创建的线程数
*/
private final int maxPoolSize = 200;
/**
* 队列最大长度
*/
private final int queueCapacity = 1000;
/**
* 线程池维护线程所允许的空闲时间
*/
private final 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()) {
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
ThreadUtils.printException(r, t);
}
};
}
}
异步任务管理器
/**
* 异步任务管理器
*
* @author LiWT
*/
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);
}
/**
* 停止任务线程池
*/
public void shutdown() {
ThreadUtils.shutdownAndAwaitTermination(executor);
}
}
关闭线程池实现
/**
* 确保应用退出时能关闭后台线程
*
* @author LiWT
*/
@Slf4j
@Component
public class ShutdownManager {
@PreDestroy
public void destroy() {
shutdownAsyncManager();
}
/**
* 停止异步执行任务
*/
private void shutdownAsyncManager() {
try {
log.info("====关闭后台任务任务线程池====");
AsyncManager.me().shutdown();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
- 可以看出异步任务管理器核心其实是创建一个10毫秒延迟的定时器,然后来触发创建的定时任务来执行业务逻辑。
- 使用时通过调用execute()方法来创建定时任务执行,在我们服务终止或关闭时调用shutdown()来停掉任务的线程池。
异步调用工厂
/**
* 异步工厂(产生任务用)
*
* @author LiWT
*/
@Slf4j
public class AsyncFactory {
/**
* 测试异步工厂
*
* @param str
* @return
*/
public static TimerTask testAsync(String str) {
return new TimerTask() {
@Override
public void run() {
ThreadUtils.sleep(10000);
long times = System.currentTimeMillis();
System.out.println("testAsync times:" + times + " log : " + str);
}
};
}
/**
* 记录登陆信息
*
* @param username 用户名
* @param status 状态
* @param message 消息
* @param request request
* @param args 列表
* @return 任务task
*/
public static TimerTask recordLogininfor(final String username, final String status, final String message, HttpServletRequest request,
final Object... args) {
return new TimerTask() {
@Override
public void run() {
//获取登录信息保存数据库
}
};
}
/**
* 操作日志记录
* aop保存用户操作日志调用
*
* @param operLog 操作日志信息
* @return 任务task
*/
public static TimerTask recordOper(final String operLog) {
return new TimerTask() {
@Override
public void run() {
//用户操作信息保存数据库
}
};
}
}
- 异步工厂中定义了testAsync()方法,可以用来进行测试。
- 下边两个是我们项目中使用的的场景:
- 比如在用户登录后记录用户登录信息;
- 或是每次用户操作完毕后保存操作日志;
- 又或是一些第三方接口回调方法的执行。
异步工厂测试用例
/**
* @Author: LiWT
* @Date: 2021/8/14 10:08
*/
@RestController
@RequestMapping("/async")
public class AsyncFactoryController {
@GetMapping("/asyncFactoryTest")
public String asyncFactoryTest() {
AsyncManager.me().execute(AsyncFactory.testAsync("异步执行......"));
long successTimes = System.currentTimeMillis();
return "请求结束,返回 时间:" + successTimes;
}
}
我们从浏览器访问测试接口:localhost:port/async/asyncFactoryTest
为了更直观的体现异步执行的时间差,我在testAsync()方法中执行了ThreadUtils.sleep(10000);使线程睡眠10s后执行。
请求直接返回响应:
请求结束,返回 时间:1628834332942
控制台输出:testAsync times:1628834342973 log : 异步执行......
通过测试我们可以看到异步操作的好处,首先及时的响应了客户端,然后再来起别的线程处理我们的业务,一定程度上优化了我们的程序。
二:使用JDK1.8中 CompletableFuture
CompletableFuture 简介
CompletableFuture是Future的实现类,弥补了Future一些缺陷,比如阻塞、回调等
CompletableFuture 实现异步
CompletableFuture提供了四个静态方法来创建一个异步操作:
public static CompletableFuture<Void> runAsync(Runnable runnable)
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
创建时若没有传入Executor,会使用ForkJoinPool.commonPool() 作为它的线程池执行异步代码;若指定了线程池,则使用指定的线程池运行。
- runAsync 方法不支持返回值
- supplyAsync 方法支持返回值
CompletableFuture 测试用例
/**
* @Author: LiWT
* @Date: 2021/8/14 10:08
*/
@RestController
@RequestMapping("/async")
public class AsyncFactoryController {
@Autowired
@Qualifier("threadPoolTaskExecutor")
ThreadPoolTaskExecutor executor;
@GetMapping("/completableFutureAsyncTest")
public void completableFutureAsyncTest() throws ExecutionException, InterruptedException {
//方式一:
CompletableFuture.runAsync(() -> {
System.out.println("runAsync 方式一");
});
//方式二:
CompletableFuture.runAsync(() -> {
System.out.println("runAsync 方式二");
}, executor);
//方式三:
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync 方式三");
return "方式三 执行完毕。。。";
});
System.out.println(future.get());
//方式四:
CompletableFuture<String> futureByExecutor = CompletableFuture.supplyAsync(() -> {
System.out.println("supplyAsync 方式四");
return "方式四 执行完毕。。。";
}, executor);
System.out.println(futureByExecutor.get());
}
}
控制台输出为:
runAsync 方式一
runAsync 方式二
supplyAsync 方式三
方式三 执行完毕。。。
supplyAsync 方式四
方式四 执行完毕。。。
CompletableFuture使用get()即可获取到返回值。
CompletableFuture常用的方法还有thenApplyAsync()、whenCompleteAsync()、thenAccept()、thenCombine()、anyOf()、allOf()…
总的来说JDK1.8中的CompletableFuture还是非常方便的,大家有空可以多学习一下。