18 场景集合:并发 List、Map的应用场景

更新时间:2019-10-08 16:45:03

一个不注意小事情的人,永远不会成功大事业。

——戴尔·卡耐基

引导语

并发 List、Map 使用最多的就是 CopyOnWriteArrayList 和 ConcurrentHashMap,在考虑 API 时,我们也无需迟疑,这两个并发类在安全和性能方面都很好,我们都可以直接使用。

并发的场景很多,但归根结底其实就是共享变量被多个线程同时访问,也就是说 CopyOnWriteArrayList 或 ConcurrentHashMap 会被作为共享变量,本节我们会以流程引擎为案例,现身说法,增加一下大家的工作经验积累。

流程引擎在实际工作中经常被使用,其主要功能就是对我们需要完成的事情,进行编排和组装,比如在淘宝下单流程中,我们一共会执行 20 个 Spring Bean,流程引擎就可以帮助我们调起 20 个 Spring Bean,并帮助我们去执行,本文介绍的重点在于如何使用 Map + List 来设计流程引擎的数据结构,以及其中需要注意到的线程安全的问题。

1 嵌套 Map,简单流程引擎

市面上有很多流程引擎,比如说 Activiti、Flowable、Camunda 等等,功能非常齐全,但我们本小节只实现一种最最简单的流程引擎,只要能对我们需要完成的事情进行编排,并能依次的调用就行。

1.1 流程引擎设计思路

我们认为每个流程都会做 4 个阶段的事情,阶段主要是指在整个流程中,大概可以分为几个大的步骤,每个阶段可以等同为大的步骤,分别如下:

  1. 参数校验,主要是对流程的入参数进行校验;
  2. 业务校验,主要是对当前流程中的业务进行逻辑校验;
  3. 事务中落库,主要把数据落库,控制事务;
  4. 事务后事件,我们在数据落库,事务提交之后,可能会做一些其他事情,比如说发消息出来等等。

以上每个大的阶段,都会做一些粒度较细的事情,比如说业务校验,我们可能会对两个业务对象进行校验,那么此时业务校验阶段就会做两件事情,每件具体的事情,我们叫做领域行为,在实际项目中,一个领域行为一般都是一个 Spring Bean。

综上所述,流程引擎嵌套数据结构就是:流程 -> 阶段 -> 领域行为,前者对应后者,都是一对一或者一对多的关系。

我们以在淘宝上买东西时,下单为例,下单指的是我们在淘宝选择好了商品和优惠券后,点击购买按钮时触发的动作。

为了方便举例,我们假设在淘宝上买电视和电影票,在后端,会分别对应着两个下单流程,我们画图示意一下:

上图中,左右两个黑色长方形大框代表着两个流程,流程下面有多个阶段,阶段用蓝色表示,每个阶段下面有多个领域行为,用红色表示。

可以看到两个流程中,都包含有四个阶段,阶段都是相同的,但每个阶段中的领域行为,有的相同,有的却是特有的。

三个概念,每个概念层层嵌套,整体组装起来,用来表示一个流程,那么这个数据结构,我们应该如何表示呢?

使用 Map + List 即可!

1.2 数据结构的定义

流程的数据结构定义分成两步:

  1. 定义出阶段、领域行为基础概念;
  2. 把阶段、领域行为、流程概念组合起来,定义出流程的数据结构。

首先给阶段定义一个枚举,如下 StageEnum 代表流程中的阶段或步骤:

public enum StageEnum {PARAM_VALID("PARAM_VALID", "参数校验"),BUSINESS_VALID("BUSINESS_VALID", "业务校验"),IN_TRANSACTION("IN_TRANSACTION", "事务中落库"),AFTER_TRANSACTION("AFTER_TRANSACTION", "事务后事件"),;private String code;private String desc;StageEnum(String code, String desc) {this.code = code;this.desc = desc;}
}

领域行为我们无需定义,目前通用的技术框架都是 Spring Boot,领域行为都是 Spring Bean,为了简单起见,我们给领域行为定义了一个接口,每个领域行为都要实现这个接口,方便我们编排,接口如下:

/*** 领域行为* author  wenhe* date 2019/8/11*/
public interface DomainAbilityBean {/*** 领域行为的方法入口*/FlowContent invoke(FlowContent content);}

接着我们使用 Map + List 来定义流程,定义如下:

/*** 第一个 key 是流程的名字* 第二个 map 的 key 是阶段,为 StageEnum 枚举,值为多个领域行为的集合*/
Map<String,Map<StageEnum,List<DomainAbilityBean>>> flowMap

至此,我们定义出了,简单流程引擎的数据结构,流程引擎看着很复杂,利用 Map + List 的组合,就巧妙的定义好了。

2 容器初始化时,本地缓存使用

我们定义好 Map 后,我们就需要去使用他,我们使用的大体步骤为:

  1. 项目启动时,把所有的流程信息初始化到 flowMap(刚刚定义的流程的数据结构叫做 flowMap) 中去,可能是从数据库中加载,也可能是从 xml 文件中加载;
  2. 查找流程时,直接从 flowMap 中获取即可。

2.1 初始化

以上两步最为关键的点就是 flowMap 必须是可以随时访问到的,所有我们会把 flowMap 作为共享变量使用,也就是会被 static final 关键字所修饰,我们首先来 mock 一下把所有信息初始化到 flowMap 中去的代码,如下:

@Component
public class FlowCenter {/*** flowMap 是共享变量,方便访问,并且是 ConcurrentHashMap*/public static final Map<String, Map<StageEnum, List<DomainAbilityBean>>> flowMap= Maps.newConcurrentMap();/*** PostConstruct 注解的意思就是* 在容器启动成功之后,执行 init 方法,初始化 flowMap*/@PostConstructpublic void init() {// 初始化 flowMap,可能是从数据库,或者 xml 文件中加载 map}}

以上代码,关键地方在于三点:

  1. flowMap 被 static final 修饰,是个共享变量,方便访问;
  2. flowMap 是 ConcurrentHashMap,所以我们所有的操作都无需加锁,比如我们在 init 方法中,对 flowMap 进行初始化,就无需加锁,因为 ConcurrentHashMap 本身已经保证了线程安全;
  3. 这里我们初始化的时机是在容器启动的时候,在实际的工作中,我们经常在容器启动的时候,把不会经常发生变动的数据,放到类似 List、Map 这样的共享变量中,这样当我们频繁要使用的时候,直接从内存中读取即可。

2.2 使用

那我们实际使用的时候,只需要告诉 flowMap 当前是那个流程的那个阶段,就可以返回该流程该阶段下面的所有领域行为了,我们写了一个流程引擎使用的工具类入口,如下:

// 流程引擎对外的 API
public class FlowStart {/*** 流程引擎开始** @param flowName 流程的名字*/public void start(String flowName, FlowContent content) {invokeParamValid(flowName, content);invokeBusinessValid(flowName, content);invokeInTramsactionValid(flowName, content);invokeAfterTramsactionValid(flowName, content);}// 执行参数校验private void invokeParamValid(String flowName, FlowContent content) {stageInvoke(flowName, StageEnum.PARAM_VALID, content);}// 执行业务校验private void invokeBusinessValid(String flowName, FlowContent content) {stageInvoke(flowName, StageEnum.BUSINESS_VALID, content);}// 执行事务中private void invokeInTramsactionValid(String flowName, FlowContent content) {stageInvoke(flowName, StageEnum.IN_TRANSACTION, content);}// 执行事务后private void invokeAfterTramsactionValid(String flowName, FlowContent content) {stageInvoke(flowName, StageEnum.AFTER_TRANSACTION, content);}// 批量执行 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) {domainAbility.invoke(content);}}}

从代码中可以看到,我们在流程引擎的入口,只要根据参数校验、业务校验、事务中、事务后四个阶段,从 flowMap 中得到领域行为的集合,然后对领域行为进行顺序执行即可。

我们在使用时,直接使用上述类的 start 方法即可。

当然以上演示的流程引擎只是一个大的框架,还有很多地方需要改进的地方,比如如何找到 flowName,如何初始化 flowMap,但这些都不是本节重点,本节主要想通过流程引擎案例来说明几点:

  1. 把 List 和 Map 作为共享变量非常常见,就像咱们这种项目启动时,从数据库中把数据捞出来,然后封装成 List 或 Map 的结构,这样做的优点就是节约资源,不用每次用的时候都去查数据库,直接从内存中获取即可;
  2. 并发场景下,我们可以放心的使用 CopyOnWriteArrayList 和 ConcurrentHashMap 两个并发类,首先用 static final 对两者进行修饰,使其成为共享变量,接着在写入或者查询的时候,无需加锁,两个 API 内部已经实现了加锁的功能了;
  3. 有一点需要澄清一下,就是 CopyOnWriteArrayList 和 ConcurrentHashMap 只能作为单机的共享变量,如果是分布式系统,多台机器的情况下,这样做不行了,需要使用分布式缓存了。

3 总结

本节内容,以流程引擎为例,说明了如何使用 Map + List 的嵌套结构设计流程引擎,以及在并发情况下,如何安全的使用 List 和 Map。

本案列是高并发项目的真实案例,感兴趣的同学可以在此流程引擎框架基础上进行细节补充,实现可运行的流程引擎。

面试官系统精讲Java源码及大厂真题 - 18 场景集合:并发 List、Map的应用场景相关推荐

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

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

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

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

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

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

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

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

  5. 面试官系统精讲Java源码及大厂真题 - 30 AbstractQueuedSynchronizer 源码解析(上)

    30 AbstractQueuedSynchronizer 源码解析(上) 不想当将军的士兵,不是好士兵. 引导语 AbstractQueuedSynchronizer 中文翻译叫做同步器,简称 AQ ...

  6. 面试官系统精讲Java源码及大厂真题 - 27 Thread 源码解析

    27 Thread 源码解析 书籍乃世人积累智慧之长明灯. 引导语 从本章开始我们开始学习线程的知识,线程是非常有趣的一个章节,大多数同学对于线程 API,属于不用就忘,到用时需要百度的情况,希望通过 ...

  7. 面试官系统精讲Java源码及大厂真题 - 34 只求问倒:连环相扣系列锁面试题

    34 只求问倒:连环相扣系列锁面试题 自信和希望是青年的特权. 引导语 面试中,问锁主要是两方面:锁的日常使用场景 + 锁原理,锁的日常使用场景主要考察对锁 API 的使用熟练度,看看你是否真的使用过 ...

  8. 面试官系统精讲Java源码及大厂真题 - 36 从容不迫:重写锁的设计结构和细节

    36 从容不迫:重写锁的设计结构和细节 受苦的人,没有悲观的权利. --尼采 引导语 有的面试官喜欢让同学在说完锁的原理之后,让你重写一个新的锁,要求现场在白板上写出大概的思路和代码逻辑,这种面试题目 ...

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

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

最新文章

  1. 无人车时代:用深度学习辅助行人检测
  2. MSB6006: “cmd.exe”已退出,代码为 3.
  3. 尚硅谷最新版JavaWeb全套教程,java web零基础入门完整版(二)
  4. 数据分析-pands分析美国选民对总统的喜好(python实现)
  5. python基本运算
  6. 账户配置阻止使用计算机.怎样开机,开机自启动设置怎么操作 开机自启动设置如何禁止【图文介绍】...
  7. Per-FedAvg:联邦个性化元学习
  8. 02-dos2unix命令与windows编辑shell不可执行问题
  9. JavaScript学习(六十三)—typeof和instanceof检测数据类型的异同
  10. Linq 学习笔记(一)
  11. html请求接口_Python 如何使用 HttpRunner 做接口自动化测试
  12. 谁说菜鸟不会数据分析
  13. css里的小图标怎么加入,前端页面如何引入小图标?CSS字体的另类使用方式!
  14. 量化投资学习-16:从波粒二象性和叠加原理看股票的三面:市场面、基本面、技术面
  15. chromedriver 下载与安装方法[转]
  16. dubbo-dubbo spi详解
  17. java文档注释用什么开头,极其重要
  18. 华为又一黑科技:AR高精地图服务即将上线
  19. 【学生必备求职指南】好简历是怎样炼成的?毕业生简历实例点评版
  20. asp毕业设计——基于asp+sqlserver的网上选课系统设计与实现(毕业论文+程序源码)——网上选课系统

热门文章

  1. 白银TD盈亏计算实例介绍
  2. CPU各寄存器的作用
  3. ASP.net与PHP两大网站开发架构优势对比
  4. Web浏览器调试工具firebug
  5. JS针对图片加载及404处理
  6. php命令行用法简介
  7. 宿主机挂载虚拟机磁盘文件guestmount
  8. MySQL安装错误——Access denied for user 'root'@'localhost' (using password: YES)
  9. LeetCode 274. H-Index
  10. s1机试补考补习 9206