技术分享
如何高效优雅的使用java枚举
00 分钟
2023-6-12
2023-6-12
type
status
date
Jun 12, 2023 01:22 AM
slug
summary
tags
category
icon
password
日期
标签 2
状态
链接地址
Column
标签

背景

枚举在系统中的地位不言而喻,状态、类型、场景、标识等等,少则十几个多则上百个,相信以下这段代码很常见,而且类似的代码到处都是,目标:消除这类冗余代码。

枚举缓存

减少代码冗余,代码简洁 去掉for循环,性能稳定高效

模块设计图

notion image

缓存结构

notion image

源码分析

源码展示

关键解读

开闭原则

什么是开闭原则?
对修改是封闭的,对新增扩展是开放的。为了满足开闭原则,这里设计成有枚举主动注册到缓存,而不是有缓存主动加载枚举,这样设计的好处就是:当增加一个枚举时只需要在当前枚举的静态块中自主注册即可,不需要修改其他的代码 比如我们现在要新增一个状态类枚举:

注册时机

将注册放在静态块中,那么静态块什么时候执行呢?
  1. 当第一次创建某个类的新实例时
  1. 当第一次调用某个类的任意静态方法时
  1. 当第一次使用某个类或接口的任意非final静态字段时
  1. 当第一次Class.forName
如果我们入StatusEnum创建枚举,那么在应用系统启动的过程中StatusEnum的静态块可能从未执行过,则枚举缓存注册失败,所有我们需要考虑延迟注册,代码如下:
Class.forName(clazz.getName())被执行的两个必备条件:
1.缓存中没有枚举class的键,也就是说没有执行过枚举向缓存注册的调用,见EnumCache.find方法对executeEnumStatic方法的调用; 2.executeEnumStatic中的LOADED.put(clazz, true);还没有被执行过,也就是Class.forName(clazz.getName());没有被执行过;
我们看到executeEnumStatic中用到了双重检查锁,所以分析一下正常情况下代码执行情况和性能:
  1. 当静态块还未执行时,大量的并发执行find查询。
  • 此时executeEnumStatic中synchronized会阻塞其他线程;第一个拿到锁的线程会执行Class.forName(clazz.getName());同时触发枚举静态块的同步执行;
  • 之后其他线程会逐一拿到锁,第二次检查会不成立跳出executeEnumStatic;
  1. 当静态块已经执行,且静态块里面正常执行了缓存注册,大量的并发执行find查询。 executeEnumStatic方法不会调用,没有synchronized引发的排队问题;
  1. 当静态块已经执行,但是静态块里面没有调用缓存注册,大量的并发执行find查询。 find方法会调用executeEnumStatic方法,但是executeEnumStatic的第一次检查通不过;
find方法会提示异常需要在静态块中添加注册缓存的代码;
总结:第一种场景下会有短暂的串行,但是这种内存计算短暂串行相比应用系统的业务逻辑执行是微不足道的,也就是说这种短暂的串行不会成为系统的性能瓶颈

样例展示

构造枚举
测试类
执行结果
SUCCESS INIT INIT SUCCESS INIT INIT

性能对比

对比代码,如果OrderType中的实例数越多性能差异会越大
 

执行结果

 

总结

1、代码简洁; 2、枚举中实例数越多,缓存模式的性能优势越多;