PART1:同步与异步
- 同步:你要泡茶,那你烧水的同时,能不能去先洗洗茶具啥的,别楞在那呀,同步就是水不开我不动,敌不动我不动
- 异步:我不楞在那,水不开我先干点其他的
- 异步编程是
让程序并发运行
的一种手段。它允许多个事件同时发生,当程序调用需要长时间运行的方法时,它不会阻塞当前的执行流程,程序可以继续运行。- 核心思路:
采用多线程优化性能,将串行操作变成并行操作
。异步模式设计的程序可以显著减少线程等待,从而在高吞吐量场景中,极大提升系统的整体性能,显著降低时延。
- 核心思路:
- 异步编程是
PART2:常见的异步编程实现方式
- 常见的异步编程实现方式
- 方式一:直接继承 Thread类 是创建异步线程最简单的方式【首先,创建 Thread 子类,普通类或匿名内部类方式;然后创建子类实例;最后通过 start()方法启动线程。】
- 当然如果每次都创建一个 Thread 线程,频繁的创建、销毁,浪费系统资源。我们可以采用线程池
@Bean(name = "executorService") public ExecutorService downloadExecutorService() { return new ThreadPoolExecutor(20, 40, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), new ThreadFactoryBuilder().setNameFormat("defaultExecutorService-%d").build(), (r, executor) -> log.error("defaultExecutor pool is full! ")); }
- 启动线程的三种基本方式有相关讲述
- 上面三种线程启动方式咱们也可以知道,有时候有些业务不仅仅要执行过程,还要获取执行结果。那么只用继承Thread达到多线程并行处理就不够了,Java 从 1.5 版本开始,提供了 Callable 和 Future,可以在任务执行完毕之后得到任务执行结果。也提供了其他功能,如:取消任务、查询任务是否完成等
- 一个例子:
public class CallableAndFuture { public static ExecutorService executorService = new ThreadPoolExecutor(4, 40, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build(), new ThreadPoolExecutor.AbortPolicy()); static class MyCallable implements Callable<String> { @Override public String call() throws Exception { return "异步处理,Callable 返回结果"; } } public static void main(String[] args) { Future<String> future = executorService.submit(new MyCallable()); try { System.out.println(future.get()); } catch (Exception e) { // nodo } finally { executorService.shutdown(); } } }
- 除了Future那种实现方式之外,FutureTask 实现了 RunnableFuture 接口,则 RunnableFuture 接口继承了 Runnable 接口和 Future 接口,所以可以将 FutureTask 对象作为任务提交给 ThreadPoolExecutor 去执行,也可以直接被 Thread 执行;又因为实现了 Future 接口,所以也能用来获得任务的执行结果。启动线程的三种基本方式中ctrl+F搜索FutureTask
- 上面三种线程启动方式咱们也可以知道,有时候有些业务不仅仅要执行过程,还要获取执行结果。那么只用继承Thread达到多线程并行处理就不够了,Java 从 1.5 版本开始,提供了 Callable 和 Future,可以在任务执行完毕之后得到任务执行结果。也提供了其他功能,如:取消任务、查询任务是否完成等
- 当然如果每次都创建一个 Thread 线程,频繁的创建、销毁,浪费系统资源。我们可以采用线程池
- 异步框架 CompletableFuture:
- Future 类通过 get() 方法阻塞等待获取异步执行的运行结果,性能比较差。JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,
CompletableFuture 可以通过回调的方式来处理计算结果,实现了异步非阻塞,性能更优
。
- CompletableFuture 提供了非常丰富的 API,大约有 50 种处理串行,并行,组合以及处理错误的方法。
- Future 类通过 get() 方法阻塞等待获取异步执行的运行结果,性能比较差。JDK1.8 中,Java 提供了 CompletableFuture 类,它是基于异步函数式编程。相对阻塞式等待返回结果,
- SpringBoot 注解 @Async
- 除了硬编码的异步编程处理方式,
SpringBoot 框架还提供了 注解式 解决方案,以 方法体 为边界,方法体内部的代码逻辑全部按异步方式执行
。 - 在 Spring Boot 应用中使用 @Async的步骤:
- 首先,
使用 @EnableAsync 启用异步注解
- 自定义线程池:
@Configuration @Slf4j public class ThreadPoolConfiguration { @Bean(name = "defaultThreadPoolExecutor", destroyMethod = "shutdown") public ThreadPoolExecutor systemCheckPoolExecutorService() { return new ThreadPoolExecutor(3, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10000), new ThreadFactoryBuilder().setNameFormat("default-executor-%d").build(), (r, executor) -> log.error("system pool is full! ")); } }
在异步处理的方法上添加注解 @Async【用 @Async 注解标记的方法,称为异步方法】,当对 execute 方法执行调用时,通过自定义的线程池defaultThreadPoolExecutor 异步化执行 execute 方法
@Service public class AsyncServiceImpl implements AsyncService { //在异步处理的方法上添加注解 @Async ,当对 execute 方法执行调用时,通过自定义的线程池 defaultThreadPoolExecutor 异步化执行 execute 方法 @Async("defaultThreadPoolExecutor") public Boolean execute(Integer num) { System.out.println("线程:" + Thread.currentThread().getName() + " , 任务:" + num); return true; } }
- 首先,
- 除了硬编码的异步编程处理方式,
- Spring ApplicationEvent 事件
- 事件机制在一些大型项目中被经常使用,Spring 专门提供了一套事件机制的接口,满足了架构原则上的解耦。
ApplicationContext 通过 ApplicationEvent 类【ApplicationEvent 是由 Spring 提供的所有 Event 类的基类】和 ApplicationListener 接口进行事件处理。如果将实现 ApplicationListener 接口的 bean 注入到上下文中,则每次使用 ApplicationContext 发布 ApplicationEvent 时,都会通知该 bean。本质上,这是标准的观察者设计模式
。- Spring 框架的事件机制默认是同步阻塞的。只是在代码规范方面做了解耦,有较好的扩展性,但底层还是采用同步调用方式。如果想实现异步调用,我们需要手动创建一个 SimpleApplicationEventMulticas。
@Component public class SpringConfiguration { @Bean public SimpleApplicationEventMulticaster applicationEventMulticaster(@Qualifier("defaultThreadPoolExecutor") ThreadPoolExecutor defaultThreadPoolExecutor) { SimpleApplicationEventMulticaster simpleApplicationEventMulticaster = new SimpleApplicationEventMulticaster(); simpleApplicationEventMulticaster.setTaskExecutor(defaultThreadPoolExecutor); return simpleApplicationEventMulticaster; } }
- 涉及到SimpleApplicationEventMulticaster 这个我们自己实例化的 Bean 与系统默认的加载顺序时是这样的,处理逻辑在 AbstractApplicationContext#initApplicationEventMulticaster 方法中,
容器会通过 beanFactory 查找是否有自定义的 Bean,如果没有,容器会自己 new 一个 SimpleApplicationEventMulticaster 对象注入到容器中
。
- 涉及到SimpleApplicationEventMulticaster 这个我们自己实例化的 Bean 与系统默认的加载顺序时是这样的,处理逻辑在 AbstractApplicationContext#initApplicationEventMulticaster 方法中,
- Spring 框架的事件机制默认是同步阻塞的。只是在代码规范方面做了解耦,有较好的扩展性,但底层还是采用同步调用方式。如果想实现异步调用,我们需要手动创建一个 SimpleApplicationEventMulticas。
- 使用步骤:
- 首先,自定义业务事件子类,继承自 ApplicationEvent,通过泛型注入业务模型参数类。相当于 MQ 的消息体。
- 然后,编写事件监听器。ApplicationListener 接口是由 Spring 提供的事件订阅者必须实现的接口,我们需要定义一个子类,继承 ApplicationListener。相当于 MQ 的消费端
@Component public class OrderEventListener implements ApplicationListener<OrderEvent> { @Override public void onApplicationEvent(OrderEvent event) { System.out.println("【OrderEventListener】监听器处理!" + JSON.toJSONString(event.getSource())); } }
- 最后,发布事件,把某个事件告诉所有与这个事件相关的监听器。相当于 MQ 的生产端。
- 首先,自定义业务事件子类,继承自 ApplicationEvent,通过泛型注入业务模型参数类。相当于 MQ 的消息体。
- 事件机制在一些大型项目中被经常使用,Spring 专门提供了一套事件机制的接口,满足了架构原则上的解耦。
- 消息队列:翻翻项目经验那里,这块了解的不多
- 方式一:直接继承 Thread类 是创建异步线程最简单的方式【首先,创建 Thread 子类,普通类或匿名内部类方式;然后创建子类实例;最后通过 start()方法启动线程。】
巨人的肩膀:
java并发编程之美
java并发编程实战
网络编程
腾讯云社区各位老师
B战各位做视频的老师
SpringForAll
微观技术老师
极客时间的《Java 并发编程实战》