40 打动面试官:线程池流程编排中的运用实战

没有智慧的头脑,就像没有蜡烛的灯笼。

——托尔斯泰

引导语

在线程池的面试中,面试官除了喜欢问 ThreadPoolExecutor 的底层源码外,还喜欢问你有没有在实际的工作中用过 ThreadPoolExecutor,我们在并发集合类的《场景集合:并发 List、Map 的应用场景》一文中说过一种简单的流程引擎,如果没有看过的同学,可以返回去看一下。

本章就在流程引擎的基础上运用 ThreadPoolExecutor,使用线程池实现 SpringBean 的异步执行。

1 流程引擎关键代码回顾

《场景集合:并发 List、Map 的应用场景》文中流程引擎执行 SpringBean 的核心代码为:

  // 批量执行 Spring Beanprivate void stageInvoke(String flowName, StageEnum stage, FlowContent content) {List<DomainAbilityBean>domainAbilitys =FlowCenter.flowMap.getOrDefault(flowName, Maps.newHashMap()).get(stage);if (CollectionUtils.isEmpty(domainAbilitys)) {throw new RuntimeException("找不到该流程对应的领域行为" + flowName);}for (DomainAbilityBean domainAbility : domainAbilitys) {// 执行 Spring BeandomainAbility.invoke(content);}}

入参是 flowName(流程名称)、stage(阶段)、content(上下文),其中 stage 中会执行很多 SpringBean,SpringBean 被执行的代码是 domainAbility.invoke(content)。

2 异步执行 SpringBean

从上述代码中,我们可以看到所有的 SpringBean 都是串行执行的,效率较低,我们在实际业务中发现,有的 SpringBean 完全可以异步执行,这样既能完成业务请求,又能减少业务处理的 rt,对于这个需求,我们条件反射的有了两个想法:

  1. 需要新开线程来异步执行 SpringBean,可以使用 Runable 或者 Callable;
  2. 业务请求量很大,我们不能每次来一个请求,就开一个线程,我们应该让线程池来管理异步执行的线程。

于是我们决定使用线程池来完成这个需求。

3 如何区分异步的 SpringBean

我们的 SpringBean 都是实现 DomainAbilityBean 这个接口的,接口定义如下:

public interface DomainAbilityBean {/*** 领域行为的方法入口*/FlowContent invoke(FlowContent content);
}

从接口定义上来看,没有预留任何地方来标识该 SpringBean 应该是同步执行还是异步执行,这时候我们可以采取注解的方式,我们新建一个注解,只要 SpringBean 上有该注解,表示该 SpringBean 应该异步执行,否则应该同步执行,新建的注解如下:

/**
* 异步 SpringBean 执行注解* SpringBean 需要异步执行的话,就打上该注解
*author  wenhe
*date 2019/10/7
*/
@Target(ElementType.TYPE)// 表示该注解应该打在类上
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AsyncComponent {}

接着我们新建了两个 SpringBean,并在其中一个 SpringBean 上打上异步的注解,并且打印出执行 SpringBean 的线程名称,如下图:

图中实现了两个 SpringBean:BeanOne 和 BeanTwo,其中 BeanTwo 被打上了 AsyncComponent 注解,表明 BeanTwo 应该被异步执行,两个 SpringBean 都打印出执行的线程的名称。

4 mock 流程引擎数据中心

《场景集合:并发 List、Map 的应用场景》一文中,我们说可以从数据库中加载出流程引擎需要的数据,此时我们 mock 一下,mock 的代码如下:

@Component
public class FlowCenter {/*** flowMap 是共享变量,方便访问*/public static final Map<String, Map<StageEnum, List<DomainAbilityBean>>> flowMap= Maps.newConcurrentMap();/*** PostConstruct 注解的意思就是* 在容器启动成功之后,初始化 flowMap*/@PostConstructpublic void init() {// 初始化 flowMap mockMap<StageEnum, List<DomainAbilityBean>> stageMap = flowMap.getOrDefault("flow1",Maps.newConcurrentMap());for (StageEnum value : StageEnum.values()) {List<DomainAbilityBean> domainAbilitys = stageMap.getOrDefault(value, Lists.newCopyOnWriteArrayList());if(CollectionUtils.isEmpty(domainAbilitys)){domainAbilitys.addAll(ImmutableList.of(ApplicationContextHelper.getBean(BeanOne.class),ApplicationContextHelper.getBean(BeanTwo.class)));stageMap.put(value,domainAbilitys);}}flowMap.put("flow1",stageMap);// 打印出加载完成之后的数据结果log.info("init success,flowMap is {}", JSON.toJSONString(flowMap));}
}

5 新建线程池

在以上操作完成之后,只剩下最后一步了,之前我们执行 SpringBean 时,是这行代码:domainAbility.invoke(content);

现在我们需要区分 SpringBean 是否是异步的,如果是异步的,丢到线程池中去执行,如果是同步的,仍然使用原来的方法进行执行,于是我们把这些逻辑封装到一个工具类中,工具类如下:

/*** 组件执行器* author  wenhe* date 2019/10/7*/
public class ComponentExecutor {// 我们新建了一个线程池private static ExecutorService executor = new ThreadPoolExecutor(15, 15,365L, TimeUnit.DAYS,new LinkedBlockingQueue<>());// 如果 SpringBean 上有 AsyncComponent 注解,表示该 SpringBean 需要异步执行,就丢到线程池中去public static final void run(DomainAbilityBean component, FlowContent content) {// 判断类上是否有 AsyncComponent 注解if (AnnotationUtils.isAnnotationPresent(AsyncComponent.class, AopUtils.getTargetClass(component))) {// 提交到线程池中executor.submit(() -> { component.invoke(content); });return;}// 同步 SpringBean 直接执行。component.invoke(content);}
}

我们把原来的执行代码替换成使用组件执行器执行,如下图:

6 测试

以上步骤完成之后,简单的流程引擎就已经完成了,我们简单地在项目启动的时候加上测试,代码如下:

更严谨的做法,是会写单元测试来测试流程引擎,为了快一点,我们直接在项目启动类上加上了测试代码。

运行之后的关键结果如下:

[main] demo.sixth.SynchronizedDemo: SynchronizedDemo init begin
[main] demo.sixth.SynchronizedDemo: SynchronizedDemo init end
[main] demo.three.flow.FlowCenter : init success,flowMap is {"flow1":{"PARAM_VALID":[{},{}],"AFTER_TRANSACTION":[{"$ref":"$.flow1.PARAM_VALID[0]"},{"$ref":"$.flow1.PARAM_VALID[1]"}],"BUSINESS_VALID":[{"$ref":"$.flow1.PARAM_VALID[0]"},{"$ref":"$.flow1.PARAM_VALID[1]"}],"IN_TRANSACTION":[{"$ref":"$.flow1.PARAM_VALID[0]"},{"$ref":"$.flow1.PARAM_VALID[1]"}]}}
o.s.j.e.a.AnnotationMBeanExporter  : Registering beans for JMX exposure on startup
[main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
[main] demo.DemoApplication : Started DemoApplication in 5.377 seconds (JVM running for 6.105)
[main] demo.three.flow.BeanOne : BeanOne is run,thread name is main
[main] demo.three.flow.BeanOne : BeanOne is run,thread name is main
[pool-1-thread-1] demo.three.flow.BeanTwo : BeanTwo is run,thread name is pool-1-thread-1
[main] demo.three.flow.BeanOne : BeanOne is run,thread name is main
[pool-1-thread-2] demo.three.flow.BeanTwo : BeanTwo is run,thread name is pool-1-thread-2
[pool-1-thread-3] demo.three.flow.BeanTwo : BeanTwo is run,thread name is pool-1-thread-3
[main] demo.three.flow.BeanOne : BeanOne is run,thread name is main
[pool-1-thread-4] demo.three.flow.BeanTwo : BeanTwo is run,thread name is pool-1-thread-4

从运行结果中,我们可以看到 BeanTwo 已经被多个不同的线程异步执行了。

总结

这是一个线程池在简单流程引擎上的运用实站,虽然这个流程引擎看起来比较简单,但在实际工作中,还是非常好用的,大家可以把代码拉下来,自己尝试一下,调试一下参数,比如当我新增 SpringBean 的时候,流程引擎的表现如何。

面试官系统精讲Java源码及大厂真题 - 40 打动面试官:线程池流程编排中的运用实战相关推荐

  1. 面试官系统精讲Java源码及大厂真题 - 35 经验总结:各种锁在工作中使用场景和细节

    35 经验总结:各种锁在工作中使用场景和细节 富贵必从勤苦得. 引导语 本章主要说一说锁在工作中的使用场景,主要以 synchronized 和 CountDownLatch 为例,会分别描述一下这两 ...

  2. 面试官系统精讲Java源码及大厂真题 - 26 惊叹面试官:由浅入深手写队列

    26 惊叹面试官:由浅入深手写队列 人生的价值,并不是用时间,而是用深度去衡量的. 引导语 现在不少大厂面试的时候会要求手写代码,我曾经看过一个大厂面试时,要求在线写代码,题目就是:在不使用 Java ...

  3. 面试官系统精讲Java源码及大厂真题 - 44 场景实战:ThreadLocal 在上下文传值场景下的实践

    44 场景实战:ThreadLocal 在上下文传值场景下的实践 开篇语 我们在 <打动面试官:线程池流程编排中的运用实战>一文中将流程引擎简单地完善了一下,本文在其基础上继续进行改造,建 ...

  4. 面试官系统精讲Java源码及大厂真题 - 38 线程池源码面试题

    38 线程池源码面试题 与有肝胆人共事,从无字句处读书. --周恩来 引导语 线程池在日常面试中占比很大,主要是因为线程池内容涉及的知识点较广,比如涉及到队列.线程.锁等等,所以很多面试官喜欢把线程池 ...

  5. 面试官系统精讲Java源码及大厂真题 - 24 举一反三:队列在 Java 其它源码中的应用

    24 举一反三:队列在 Java 其它源码中的应用 世上无难事,只要肯登攀. 引导语 队列除了提供 API 供开发者使用外,自身也和 Java 中其他 API 紧密结合,比如线程池和锁,线程池直接使用 ...

  6. 面试官系统精讲Java源码及大厂真题 - 07 List 源码会问哪些面试题

    07 List 源码会问哪些面试题 勤学如春起之苗,不见其增,日有所长. --陶潜 引导语 List 作为工作中最常见的集合类型,在面试过程中,也是经常会被问到各种各样的面试题,一般来说,只要你看过源 ...

  7. 面试官系统精讲Java源码及大厂真题 - 01 开篇词:为什么学习本专栏

    01 开篇词:为什么学习本专栏 更新时间:2019-10-30 10:08:31 才能一旦让懒惰支配,它就一无可为. --克雷洛夫 不为了源码而读源码,只为了更好的实践 你好,我是文贺,Java 技术 ...

  8. java源码pdf_面试官系统精讲Java源码及大厂真题 PDF 下载

    主要内容: 第 1 章 基础 01 开篇词:为什么学习本专栏 不为了源码而读源码,只为了更好的实践 你好,我是文贺,Java 技术专家,DDD 和业务中台的资深实践者,一周面试 2-3 次的面试官. ...

  9. 面试官系统精讲Java源码及大厂真题 - 48 一起看过的 Java 源码和面试真题

    48 一起看过的 Java 源码和面试真题 不为了源码而读源码,只为了更好的实践 持续几个月,我们的专栏终于结束了,这篇总结篇,我们又想回到当初写这篇专栏的初心:我们不为读源码而读源码,只是为了更好的 ...

最新文章

  1. 混迹于IT纯屌界中独一无二的丸子
  2. 【事件】对战微信,阿里企业级社交产品“钉钉”的深度解读
  3. 天翼云从业认证(4.9)工业企业上云解决方案
  4. android系统的发展态势,2020年安卓手机发展的7个趋势,只有延伸,并无革命性的变化...
  5. 【OS学习笔记】十 实模式:实现一个程序加载器-程序加载器如何将用户程序加载到内存并执行
  6. 别再瞎搞数仓了!BAT内部大神:数据仓库不是谁都可以建的
  7. python爬取小说基本信息_Python爬虫零基础实例---爬取小说吧小说内容到本地
  8. 如何提取html的文本,如何从html标签之间提取文本?
  9. C++ 17 标准正式发布:终于能更简单地编写和维护代码了!
  10. Intellij IDEA 报错java.lang.NoClassDefFoundError
  11. LDA算法和PCA算法的总结(原理和思想)
  12. android 看视频播放器下载,想看视频播放器
  13. java毕业设计易医就医购药交互平台Mybatis+系统+数据库+调试部署
  14. ROS dst-nat端口映射限制访问映射IP
  15. Android Q 下拉状态栏快捷开关解析
  16. 在Markdown编辑器中输入上标下标
  17. Python实现相空间重构求关联维数——GP算法、自相关法求时间延迟tau、最近邻算法求嵌入维数m
  18. java nanotime 重复_关于Java中System.currentTimeMillis和System.nanoTime的错误认识
  19. Autoware1.15 + OpenPlanner2.5 下的laneChange解析(1)
  20. usb gadget printer驱动分析

热门文章

  1. 服务器提交协议冲突 Section=ResponseStatusLine 的解决办法
  2. JS代码实例:实现随机加载不同的CSS样式
  3. iframe框架页面实现自适应高度解决方案
  4. SVG 教程 (五)文本,Stroke 属性,SVG 滤镜,SVG 模糊效果
  5. Linux Shell脚本的10个有用的“面试问题和解答”
  6. UIControl IOS控件编程
  7. 机器学习速成课程 | 练习 | Google Development——编程练习:验证
  8. matplotlib+numpy绘制二维条形直方图
  9. LeetCode 303. Range Sum Query - Immutable
  10. 新到的电脑BIOS中无法识别U盘