JAVA异步编程

本文探讨了Java中的异步编程实践,包括异步工厂的实现、线程池配置、使用CompletableFuture进行异步操作,并展示了如何通过实例演示其在登录记录、日志保存等场景的应用,以及JDK1.8 CompletableFuture的简洁用法。
摘要由CSDN通过智能技术生成

前言

今天来简单总结一下JAVA中的异步执行,那么有异步肯定就有同步,他们有什么区别呢?我们为什么要进行异步操作呢?

  • 同步:在所有操作执行完毕后返回给客户端,在这个过程中需要在不断的等待,且客户端不能关闭。
  • 异步:将本次请求放入消息队列,直接反馈给用户结果,然后由程序去按照队列顺序执行业务逻辑。

那么了解了同步与异步的区别之后,我们自然也就能明白为什么要进行异步操作了。
在某些比较特殊的场合下,我们需要及时给出响应,又不想因为业务逻辑的执行导致需要等待的时候,就需要用到我们今天的主角“异步”了。

异步的使用场景:
  1. 不涉及共享资源,或对共享资源只读,即非互斥操作
  2. 没有时序上的严格关系
  3. 不需要原子操作,或可以通过其他方式控制原子性
  4. 常用于IO操作等耗时操作,因为比较影响客户体验和使用性能
  5. 不影响主线程逻辑
异步的好处:
  1. 异步流程可以立即给调用方返回初步的结果。
  2. 异步流程可以延迟给调用方最终的结果数据,在此期间可以做更多额外的工作,例如结果记录等等。
  3. 异步流程在执行的过程中,可以释放占用的线程等资源,避免阻塞,等到结果产生再重新获取线程处理。
  4. 异步流程可以等多次调用的结果出来后,再统一返回一次结果集合,提高响应效率。

异步的使用场景及好处参考链接:同步、异步的使用场景及好处

在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还是非常方便的,大家有空可以多学习一下。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值