百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

锐眼洞察|Java 8 年度实践总结

bigegpt 2024-08-27 11:54 2 浏览

Java 8 自 2014 年发布至今已有 3 个年头,但直到今年初才真正开始使用某些特性,之前最多是指定其为运行时环境。2017 年关将近,决定写篇文章记录一下近一年的实践历程,主要涵盖一下内容:

  • Lambda 表达式

  • Stream

  • 并发包

  • 集合

在开始引入新特性前,做个规划是少不了的,由于 SDMK 是微服务的架构,且每个服务支持灰度发布,因此影响全局的风险小很多,至于选择引入的服务模块,最终我选择了负责服务配置的 Console 和服务调用接入的 Gateway。

选择 Console 是因为它本身是基于 Java 8 的环境,但是随着演进,单元测试(UT)已经远远滞后,正好可以在 UT 中引入 Lambda 和 Stream,这样既不影响功能又可以踩坑,待熟悉后即可应用到功能开发或重构中。

选择 Gateway 是因为相比其他稳定的服务,Gateway 还有大量的新功能和性能重构需求。接下来进入正题,以下内容涵盖了这一年的实践过程中用到的一些新特性总结。

1. Lambda 表达式

因为 Java 8 其他的特性几乎都是依赖 Lambda 实现的,因此本节会先介绍一下 Java 8 中的 Lambda 表达式,目的是为了让还不熟悉的同学能够对 Lambda 有个初步了解。

提起 Java 8 的特性,首先肯定是 Lambda。那么什么是 Lambda 表达式,我的理解就是一段程序代码,是引入函数式编程的一种实现,基于这样的特性,Java 实现了对行为的抽象。

让我们来看一个 Lambda 表达式:

  1. s-> System.out.println(s)

-> 右边是函数定义(描述行为的代码),左边是传递给这段代码的参数。接下来再看看具体调用:

  1. List<String> list = new ArrayList<>();

  2. …… //other operation

  3. list.forEach(s -> System.out.println(s)); //打印集合中的每个元素

上述代码虽然没有实际作用,但是足以用来说明 Java 提供的函数式编程的特性了:

  • 减少模板代码,提高可读性:可以看到传递给 List 的 forEach 方法的是行为而不再是变量或对象(参数行为化),这样可以使开发人员只关注具体业务,不必再写循环或使用匿名类了。

  • 延迟执行:System.out.println 不是在传递给 forEach 的方法时立即执行,关于这点的优势在下面的并发包会有详解。

那么 Java 是怎么实现 Lambda 的执行呢?先来看一下 forEach 方法声明:

  1. public void forEach(Consumer<? super E> action)

它的参数是一个接口,但是 Oracle 的 JVM 并没有在编译时把 Lambda 表达式

简单地变成匿名类,而是在字节码中使用了 InvokeDynamic 指令,Lambda 表达式只有在运行时才会动态被编译成字节码,参数和返回值都会从调用上下文中进行推断。

而且如果 Lambda 表达式是无状态的话,在内存中只会有一个对象对应该表达式。

上面简单介绍了一下 Lambda 表达式,在实际工作中我是如何使用的呢?举一个在 Console 中重构的例子,由于业务的需要,Console 中的代码存在大量校验逻辑,如下图:

这些方法的逻辑除了生成和返回的业务实体不一样以外,其他完全一样,因此在重构后我提供了一个公共方法:

而原来的方法都重构为:

可以看到少了很多模板代码,清爽了许多,可读性也提高了,而且由于抽象到一个公共方法中,非常有利于代码及早进行 JIT 优化。

通过 Jacoco 的统计分析,在重构后,相比应用 Lambda 表达式之前,Console 的代码减少了 20% 左右。

2. Stream

Stream 是 Java 8 提供的另一个重要特性,为集合数据提供了一种基于流式的处理机制,从我实际使用的感受上来看,它屏蔽了数据集的底层处理细节,通过 Lambda 表达式让开发人员更多地关注业务处理而非迭代。下面是在 SDMK 中的几个 Stream 例子:

  1. counters.stream().mapToLong(s -> NumberUtils.toLong(s, 0l)).sum();

  2. receipts.stream().filter(receipt ->MAIL_VALIDATOR.matcher(receipt).matches()).collect(Collectors.toList());

  3. assertFalse(records.stream().mapToInt(SimpleServiceEntity::getStatus).anyMatch(s -> s != SERVICE_STATUS_ONSELL));

可以看到,没有了以往迭代的模板代码,通过 fluent 式的叠加完成数据的业务操作,使用起来都非常简单和直观。

如果需要对集合进行并行化处理,Java 还提供了创建或者转化为并行流的 API。通过将执行细节进行屏蔽,不仅简化了集合的处理代码,更重要的是,开发人员再也不用编写复杂易错的并行代码了,同时对于执行机制也可进行单独优化。

例如,现在使用 ForkJoinPool 执行并行流,将来可能会使用更高效的处理框架,但是这些对上层业务代码是透明的。

另外,在使用 Stream 时虽然简单,还需要几点注意:

  • 使用 Stream 时请摒弃循环或迭代的思想

  • 所有的操作不是按调用顺序分步执行的,而是延迟到流“启动”后同时执行的。比如上面实例 3,不是先对集合先映射完再查找匹配,可以理解之前都是在攒组合大招的每个招式(中间操作如 map,filter),直到最后一个方法才出招(终止操作如 sum),流一旦执行完毕(终止操作执行完)就不再可用了

  • Stream 的操作不会改变原始集合

  • 并行流的处理简单但不是没有代价,它要求所有操作都是无状态的或者是线程安全的,而且尽量与顺序无关(提高并行效率)

  • 虽然串行流和并行流可以随时互转(sequential () 和 parallel()),但是在流执行时的最终类型是以终止操作前最后一次指定的类型为准

3. 并发包

3.1 ConcurrentHashMap

Java 8 对 ConcurrentHashMap 大量增强,包括 long 型的 mappingCount 代表 map 的大小;使用树形结构取代链表,在 Key 实现了 Comparable 接口时更有效率;支持 Lambda 的原子操作等。

在实际使用时我用的较多的新特性是 computeIfAbsent,在 Java 8 之前如果要创建多个线程使用的缓存需要使用 putIfAbsent,但是比较繁琐,具体如下:

  1. public static AtomicLong createTableAuditInfo(String table) {

  2. AtomicLong newAudit = new AtomicLong();

  3. AtomicLong temp;

  4. if ((temp = audits.putIfAbsent(table, newAudit)) == null) {

  5. return newAudit;

  6. } else {

  7. return temp;

  8. }

  9. }

由于 putIfAbsent 操作的 Key 如果在 Map 中不存在时会返回 null,所以这个代码非常变扭,更重要的是每次执行的时候都不可避免地要创建一个新对象!(TDU 注:全文少有的感叹号表达了志刚同学对太多新对象的苦恼)

虽然可以在调用前用 get 判断,但如果 Key 不存在时还是会创建一批不需要的对象!(TDU 注:again,哪位缺对象的同学赶快来帮忙分担一下吧)在 Java 8 中可以使用如下代码:

一行代码搞定,而且由于创建对象的 Lambda 是延迟执行的,加上操作本身是原子的,所以就保证有且只有一个对象被创建。当然,在高并发下为了避免 computeIfAbsent 每次读时都会进入同步的机制,同样可以在调用前使用 get 进行一次判断。

3.2 ConcurrentHashSet?

这个一直就不存在,但是可以使用 ConcurrentHashMap 的 keySet 方法创建一个等价的 set,只不过在 Java 8 前创建的 set 没有 add 方法。

Java 8 增加了一个 keySet(V mappedValue)方法,这样用该方法创建的 set 就可以添加元素了。

注意的是用 keySet 创建的 set 不能独立存在,所有操作都最终反映到对应的 ConcurrentHashMap 上。

3.3 AtomicLong

主要用的是它提供的支持 Lambda 表达式的原子操作,如 getAndUpdate(LongUnaryOperator),getAndAccumulate 等,这样可以避免自旋使用 CAS 操作的模板代码。

3.4 LongAdder

AtomicLong 和 AtomicInteger 虽然支持原子操作,但是在高并发下更新由于需要不停的自旋重试,极大的降低性能,因此在 gateway 流控计数器中选用了 LongAdder,它的思想就是空间换时间,通过将数值分散在多个内存空间,减少并发修改的几率来提高性能。

当然这样做也有代价,就是 sum 和 sumThenReset 方法不保证是原子的,也就是在调用这些方法时如果存在并发写的话,可能会不符合操作预期。当然由于 Gateway 的流控计数器不需要精确的计数,因此在使用上没有问题。与 LongAdder 类似的还有 LongAccumulator,它可以执行更通用的累进式运算。

4. 集合

在 Java 8 中,集合类除了支持 Stream 外,最大的增强就是提供了大量的支持 Lambda 表达式的方法了,在我实践的项目中用得最多的就是 forEach(Consumer),主要是用来避免使用迭代的模板代码。

5. 总结

在近一年的实践中,由于工作和时间的关系,我并未能尝试所有的特性,包括 Stream 中的收集方法(用于将流转换为其他类型,如 List)和 Collector,集合中大量支持 lambda 的方法,新的日期时间对象,以及其他(字符串、数字、文件等等)一些增强。

但就已经实践的内容来说,Java 8 提供的这些特性还是非常强悍与实用的,在提高编码效率和代码性能方面会非常有帮助,另外对于固有的设计模式,我想也会有一定的影响,例如策略模式、模板模式等等。

总之,放心拥抱 Java 8 的新特性吧,当然,如果是旧系统迁移,请做好单元测试!

相关推荐

10w qps缓存数据库——Redis(redis缓存调优)

一、Redis数据库介绍:Redis:非关系型缓存数据库nosql:非关系型数据库没有表,没有表与表之间的关系,更不存在外键存储数据的形式为key:values的形式c语言写的服务(监听端口),用来存...

Redis系列专题4--Redis配置参数详解

本文基于windowsX64,3.2.100版本讲解,不同版本默认配置参数不同在Redis中,Redis的根目录中有一个配置文件(redis.conf,windows下为redis.windows....

开源一夏 | 23 张图,4500 字从入门到精通解释 Redis

redis是目前出场率最高的NoSQL数据库,同时也是一个开源的数据结构存储系统,在缓存、数据库、消息处理等场景使用的非常多,本文瑞哥就带着大家用一篇文章入门这个强大的开源数据库——Redis。...

redis的简单与集群搭建(redis建立集群)

Redis是什么?是开源免费用c语言编写的单线程高性能的(key-value形式)内存数据库,基于内存运行并支持持久化的nosql数据库作用主要用来做缓存,单不仅仅是做缓存,比如:redis的计数器生...

推荐几个好用Redis图形化客户端工具

RedisPlushttps://gitee.com/MaxBill/RedisPlusRedisPlus是为Redis可视化管理开发的一款开源免费的桌面客户端软件,支持Windows、Linux...

关于Redis在windows上运行及fork函数问题

Redis在将数据库进行持久化操作时,需要fork一个进程,但是windows并不支持fork,导致在持久化操作期间,Redis必须阻塞所有的客户端直至持久化操作完成。微软的一些工程师花费时间在解决在...

你必须懂的Redis十大应用场景(redis常见应用场景)

Redis作为一款高性能的键值存储数据库,在互联网业务中有着广泛的应用。今天,我们就来详细盘点一下Redis的十大常用业务场景,并附上Golang的示例代码和简图,帮助大家更好地理解和应用Redis。...

极简Redis配置(redis的配置)

一、概述Redis的配置文件位于Redis安装目录下,文件名为redis.conf(Windows名为redis.windows.conf,linux下的是redis.conf)你可以通过C...

什么是redis,怎么启动及如何压测

从今天起咱们一起来学习一下关于“redis监控与调优”的内容。一、Redis介绍Redis是一种高级key-value数据库。它跟memcached类似,不过数据可以持久化,而且支持的数据类型很丰富。...

一款全新Redis UI可视化管理工具,支持WebUI和桌面——P3X Redis UI

介绍P3XRedisUI这是一个非常实用的RedisGUI,提供响应式WebUI访问或作为桌面应用程序使用,桌面端是跨平台的,而且完美支持中文界面。Githubhttps://github....

windows系统的服务器快速部署java项目环境地址

1、mysql:https://dev.mysql.com/downloads/mysql/(msi安装包)2、redis:https://github.com/tporadowski/redis/r...

window11 下 redis 下载与安装(windows安装redis客户端)

#热爱编程是一种怎样的体验#window11下redis下载与安装1)各个版本redis下载(windows)https://github.com/MicrosoftArchive/r...

一款轻量级的Redis客户端工具,贼好用!

使用命令行来操作Redis是一件非常麻烦的事情,我们一般会选用客户端工具来操作Redis。今天给大家分享一款好用的Redis客户端工具TinyRDM,它的界面清新又优雅,希望对大家有所帮助!简介Ti...

一个.NET开发且功能强大的Windows远程控制系统

我们致力于探索、分享和推荐最新的实用技术栈、开源项目、框架和实用工具。每天都有新鲜的开源资讯等待你的发现!项目介绍SiMayRemoteMonitorOS是一个基于Windows的远程控制系统,完...

Redis客户端工具详解(4款主流工具)

大家好,我是mikechen。Redis是大型架构的基石,也是大厂最爱考察内容,今天就给大家重点详解4款Redis工具@mikechen本篇已收于mikechen原创超30万字《阿里架构师进阶专题合集...