当前位置: 首页 > 科技观察

ObjectMapper,不要像傻子一样保留New!

时间:2023-03-12 19:46:13 科技观察

自从国产fastjson频频火爆以来,jacksonjson的使用也越来越广泛。尤其是spring家族已经将其设为默认的json处理包,jackson的使用量呈爆发式增长。很多同学发现jackson没有像fastjson的JSON.parseObject这样的方法,看着确实快。要解析json,您必须创建一个新的ObjectMapper来处理真正的解析动作。如下图。publicStringgetCarString(Carcar){ObjectMapperobjectMapper=newObjectMapper();字符串str=objectMapper.writeValueAsString(汽车);returnstr;}这种代码在CV工程师手中遍地开花。魔法。这段代码有问题吗?如果你说它有问题,它确实执行正确。要说没问题,在追求性能的同学眼里,这一定是一段令人发指的代码。通用工具类是单例和线程安全的。ObjectMapper也不例外,它也是线程安全的,你可以并发执行它没有任何问题。这段代码,ObjectMapper会在每次方法调用时生成一个。除了在年轻代造成一定的内存浪费,执行时间上有没有什么破绽?新的和不新的真的有这么大的区别吗?有一次,xjjdog含蓄地指出某段被频繁调用的代码问题,被小伙伴大骂以示证据。证据?这个还得搬出Java中的基准测试工具JMH才知道。JMH(JavaMicrobenchmarkHarness)就是这样一个可以做基准测试的工具。如果您通过我们的系列工具定位到热点代码,测试其性能数据,评估改进情况,就可以交给JMH。其测量精度非常高,可达纳秒级。JMH是一个jar包,与单元测试框架JUnit非常相似,可以通过注解进行一些基本的配置。这部分的很多配置都可以通过main方法的OptionsBuilder来设置。上图是典型的JMH程序执行的内容。通过启动多进程多线程,先进行warm-up,再进行迭代,最后汇总所有测试数据进行分析。执行前后,还可以根据粒度来处理一些前后操作。JMH测试结果为了测试上述场景,我们创建了以下基准类。分为三种测试场景:直接在方法中newObjectMapper,全局使用ThreadLocal共享一个ObjectMapper,每个线程共享一个ObjectMapper。这样的测试是CPU密集型的。我的cpu是10核,直接分配10个线程的并发,测试的时候cpu跑满了。@BenchmarkMode({Mode.Throughput})@OutputTimeUnit(TimeUnit.SECONDS)@State(Scope.Thread)@Warmup(iterations=5,time=1,timeUnit=TimeUnit.SECONDS)@Measurement(iterations=5,time=1,timeUnit=TimeUnit.SECONDS)@Fork(1)@Threads(10)publicclassObjectMapperTest{Stringjson="{\"color\":\"Black\",\"type\":\"BMW\"}";@State(Scope.Benchmark)publicstaticclassBenchmarkState{ObjectMapperGLOBAL_MAP=newObjectMapper();ThreadLocalGLOBAL_MAP_THREAD=newThreadLocal<>();}@BenchmarkpublicMapglobalTest(BenchmarkStatestate)抛出异常{Mapmap=state.GLOBAL_MAP.readValue(json,Map.class);返回地图;}@BenchmarkpublicMapglobalTestThreadLocal(BenchmarkStatestate)throwsException{if(null==state.GLOBAL_MAP_THREAD.get()){state.GLOBAL_MAP_THREAD.set(newObjectMapper());}地图map=state.GLOBAL_MAP_THREAD.get().readValue(json,Map.class);返回地图;}@BenchmarkpublicMaplocalTest()throwsException{ObjectMapperobjectMapper=newObjectMapper();地图map=objectMapper.readValue(json,Map.class);返回地图;}publicstaticvoidmain(String[]args)throwsException{Optionsopts=newOptionsBuilder().include(ObjectMapperTest.class.getSimpleName()).resultFormat(ResultFormatType.CSV).build();新亚军(选择)。运行();}}测试结果如下BenchmarkModeCntScoreErrorUnitsObjectMapperTest.globalTestthrpt525125094.559±1754308.010ops/sObjectMapperTest.globalTestThreadLocalthrpt531780573.549±7779240.155ops/sObjectMapperTest.localTestthrpt52131394.345±216974.682ops/s从测试结果可以看出,如果我们每次调用都new一个ObjectMapper每秒可以执行200万次JSON解析;如果全局使用一个ObjectMapper,它每秒可以执行超过2000万次,快10倍。如果使用ThreadLocal方式,给每个线程分配一个解析器,性能会略有提升,但不会达到很夸张的程度。所以在项目中写代码的时候,我们只需要保证有一个全局的ObjectMapper即可。当然,由于ObjectMapper有很多特性需要配置,你可以针对不同的应用场景分配一个单独的ObjectMapper。总之,它的数量不需要多,因为它是线程安全的。完了,这样结论就更清楚了。我们只需要在整个项目中使用一个ObjectMapper。没必要每次都傻傻的新。毕竟性能差了10倍。如果你的JSON有很多自定义配置,使用全局变量更能凸显它的优势。不要觉得这样做没有必要,良好的编码习惯总是好的。高性能代码是一点一滴积累起来的。不积步,方能达千里。没有小水流的积聚,就不可能形成河流和海洋,这就是这个道理。作者简介:品味小姐姐(xjjdog),一个不允许程序员走弯路的公众号。专注于基础架构和Linux。十年架构,每天百亿流量,与你探讨高并发世界,给你不一样的滋味。