Java 8无人谈及的八大功能

2024-02-18 02:58
文章标签 java 功能 八大 无人 谈及

本文主要是介绍Java 8无人谈及的八大功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

时间戳锁

一直以来,多线程代码是服务器开发人员的毒药(问问Oracle的Java语言架构师和并行开发大师Brian Goetz)。Java的核心库不断加入各种复杂的用法来减少访问共享资源时的线程等待时间。其中之一就是经典的读写锁(ReadWriteLock),它让你把代码分成两部分:需要互斥的写操作和不需要互斥的读操作。

表面上看起来很不错。问题是读写锁有可能是极慢的(最多10倍),这已经和它的初衷相悖了。Java 8引入了一种新的读写锁——叫做时间戳锁。好消息是这个家伙真的非常快。坏消息是它使用起来更复杂,有更多的状态需要处理。并且它是不可重入的,这意味着一个线程有可能跟自己死锁。

时间戳锁有一种“乐观”模式,在这种模式下每次加锁操作都会返回一个时间戳作为某种权限凭证;每次解锁操作都需要提供它对应的时间戳。如果一个线程在请求一个写操作锁的时候,这个锁碰巧已经被一个读操作持有,那么这个读操作的解锁将会失效(因为时间戳已经失效)。这个时候应用程序需要从头再来,也许要使用悲观模式的锁(时间戳锁也有实现)。你需要自己搞定这一切,并且一个时间戳只能解锁它对应的锁——这一点必须非常小心。

下面我们来看一下这种锁的实例——

long stamp = lock.tryOptimisticRead(); // 非阻塞路径——超级快
work(); // 我们希望不要有写操作在这时发生
if (lock.validate(stamp)){//成功!没有写操作干扰 
}
else {//肯定同时有另外一个线程获得了写操作锁,改变了时间戳//懒汉说——我们切换到开销更大的锁吧stamp = lock.readLock(); //这是传统的读操作锁,会阻塞try {//现在不可能有写操作发生了work();}finally {lock.unlock(stamp); // 使用对应的时间戳解锁}
}

并发加法器

Java 8另一个出色的功能是并发“加法器”,它对大规模运行的代码尤其有意义。一种最基本的并发模式就是对一个计数器的读写。就其本身而言,现今处理这个问题有很多方法,但是没有一种能比Java 8提供的方法高效或优雅。

来看Java 8的LongAdder。这一系列类为大量并行读写数值的代码提供了方便的解决办法。使用超级简单。只要初始化一个LongAdder对象并使用它的add()和intValue()方法来累加和采样计数器。到目前为止,这个问题是用原子类(Atomics)来解决的,它直接利用了CPU的“比较并交换”指令(CAS)来测试并设置计数器的值。问题在于当一条CAS指令因为竞争而失败的时候,AtomicInteger类会死等,在无限循环中不断尝试CAS指令,直到成功为止。在发生竞争概率很高的环境中,这种实现被证明是非常慢的。

这和旧的Atomic类的区别在于,当CAS指令因为竞争而失败时,Adder不会一直占着CPU,而是为当前线程分配一个内部cell对象来存储计数器的增量。然后这个值和其他待处理的cell对象一起被加到intValue()的结果上。这减少了反复使用CAS指令或阻塞其他线程的可能性。

如果你问你自己,什么时候应该用并发加法器而不是原子类来管理计数器?简单的答案就是——一直这么做。

并行排序

正像并发加法器能加速计数一样,Java 8还实现了一种简洁的方法来加速排序。这个秘诀很简单。你不再这么做:

Array.sort(myArray);

而是这么做:

Arrays.parallelSort(myArray);

这会自动把目标数组分割成几个部分,这些部分会被放到独立的CPU核上去运行,再把结果合并起来。这里唯一需要注意的是,在一个大量使用多线程的环境中,比如一个繁忙的Web容器,这种方法的好处就会减弱(降低90%以上),因为越来越多的CPU上下文切换增加了开销。

切换到新的日期接口

Java 8引入了全新的date-time接口。当前接口的大多数方法都已被标记为deprecated,你就知道是时候推出新接口了。新的日期接口为Java核心库带来了易用性和准确性,而以前只能用Joda time才能达到这样的效果(译者注:Joda time是一个第三方的日期库,比Java自带的库更友好更易于管理)。

跟任何新接口一样,好消息是接口变得更优雅更强大。但不幸的是还有大量的代码在使用旧接口,这个短时间内不会有改变。

为了衔接新旧接口,历史悠久的Date类新增了toInstant()方法,用于把Date转换成新的表示形式。当你既要享受新接口带来的好处,又要兼顾那些只接受旧的日期表示形式的接口时,这个方法会显得尤其高效。

控制操作系统进程

想在你的代码里启动一个操作系统进程,通过JNI调用就能完成——但这个东西总令人一知半解,你很有可能得到一个意想不到的结果,并且一路伴随着一些很糟糕的异常。

即便如此,这是无法避免的事情。但进程还有一个讨厌的特性就是——它们搞不好就会变成僵尸进程。目前从Java中运行进程带来的问题是,进程一旦启动就很难去控制它。

为了帮我们解决这个问题,Java 8在Process类中引入了三个新的方法

  1. destroyForcibly——结束一个进程,成功率比以前高很多。
  2. isAlive——查询你启动的进程是否还活着。
  3. 重载了waitFor(),你现在可以指定等待进程结束的时间了。进程成功退出后这个接口会返回,超时的话也会返回,因为你有可能要手动终止它。

这里有两个关于如何使用这些新方法的好例子——

  • 如果进程没有在规定时间内退出,终止它并继续往前走。
if (process.wait(MY_TIMEOUT, TimeUnit.MILLISECONDS)){
//成功 }
else {
process.destroyForcibly();
}
  • 在你的代码结束前,确保所有的进程都已退出。僵尸进程会逐渐耗尽系统资源。
for (Process p : processes) {if (p.isAlive()) {p.destroyForcibly();}
}

精确的数字运算

数字溢出会导致一些讨厌的bug,因为它本质上不会出错。在一些系统中,整型值不停地增长(比如计数器),溢出的问题就尤为严重。在这些案例里面,产品在演进阶段运行得很好,甚至商用后的很长时间内也没问题,但最终会出奇怪的故障,因为运算开始溢出,产生了完全无法预料的值。

为了解决这个问题,Java 8为Math类添加了几个新的“精确型”方法,以便保护重要的代码不受溢出的影响,它的做法是当运算超过它的精度范围的时候,抛出一个未检查的ArithmeticException异常。

int safeC = Math.multiplyExact(bigA, bigB); 
// 如果结果超出+-2^31,就会抛出ArithmeticException异常

唯一不好的地方就是你必须自己找出可能产生溢出的代码。无论如何,没有什么自动的解决方案。但我觉得有这些接口总比没有好。

安全的随机数发生器

在过去几年中Java一直因为安全漏洞而饱受诟病。无论是否合理,Java已经做了大量工作来加强虚拟机和框架层,使之免受攻击。如果随机数来源于随机性不高的种子,那么那些用随机数来产生密钥或者散列敏感信息的系统就更易受攻击。

到目前为止,随机数发生算法由开发人员来决定。但问题是,如果你想要的算法依赖于特定的硬件、操作系统、虚拟机,那你就不一定能实现它。这种情况下,应用程序倾向于使用更弱的默认发生器,这就使他们暴露在更大的风险下了。

Java 8添加了一个新的方法叫SecureRandom.getInstanceStrong(),它的目标是让虚拟机为你选择一个安全的随机数发生器。如果你的代码无法完全掌控操作系统、硬件、虚拟机(如果你的程序部署到云或者PaaS上,这是很常见的),我建议你认真考虑一下使用这个接口。

可选引用

空指针就像“踢到脚趾”一样——从你学会走路开始就伴随着你,无论现在你有多聪明——你还是会犯这个错。为了帮助解决这个老问题,Java 8引入了一个新模板叫Optional<T>。

这个模板是从Scala和Hashkell借鉴来的,用于明确声明传给函数或函数返回的引用有可能是空的。有了它,过度依赖旧文档或者看过的代码经常变动的人,就不需要去猜测某个引用是否可能为空。

Optional<User> tryFindUser(int userID) {

void processUser(User user, Optional<Cart> shoppingCart) {

Optional模板有一套函数,使得采样它更方便,比如isPresent()用来检查这个值是不是非空,或者ifPresent()你可以传递一个Lambda函数过去,如果isPresent()返回true,这个Lambda函数就会被执行。不好的地方就跟Java 8的新日期接口一样,等这种模式逐渐流行,渗透到我们使用的库中和日常设计中,需要时间和工作量。

用新的Lambda语法打印Optional值:

value.ifPresent(System.out::print);

关于作者

Tal Weiss是Takipi的CEO。过去十五年中,Tal一直在设计大规模的实时的Java和C++应用。可是他仍然陶醉于分析有挑战性的bug,以及评估Java代码的性能。业余时间他喜欢爵士鼓。

查看英文原文: 8 Great Java 8 Features No One's Talking about


感谢崔康对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。

这篇关于Java 8无人谈及的八大功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/719827

相关文章

idea maven编译报错Java heap space的解决方法

《ideamaven编译报错Javaheapspace的解决方法》这篇文章主要为大家详细介绍了ideamaven编译报错Javaheapspace的相关解决方法,文中的示例代码讲解详细,感兴趣的... 目录1.增加 Maven 编译的堆内存2. 增加 IntelliJ IDEA 的堆内存3. 优化 Mave

Java String字符串的常用使用方法

《JavaString字符串的常用使用方法》String是JDK提供的一个类,是引用类型,并不是基本的数据类型,String用于字符串操作,在之前学习c语言的时候,对于一些字符串,会初始化字符数组表... 目录一、什么是String二、如何定义一个String1. 用双引号定义2. 通过构造函数定义三、St

springboot filter实现请求响应全链路拦截

《springbootfilter实现请求响应全链路拦截》这篇文章主要为大家详细介绍了SpringBoot如何结合Filter同时拦截请求和响应,从而实现​​日志采集自动化,感兴趣的小伙伴可以跟随小... 目录一、为什么你需要这个过滤器?​​​二、核心实现:一个Filter搞定双向数据流​​​​三、完整代码

SpringBoot利用@Validated注解优雅实现参数校验

《SpringBoot利用@Validated注解优雅实现参数校验》在开发Web应用时,用户输入的合法性校验是保障系统稳定性的基础,​SpringBoot的@Validated注解提供了一种更优雅的解... 目录​一、为什么需要参数校验二、Validated 的核心用法​1. 基础校验2. php分组校验3

Java Predicate接口定义详解

《JavaPredicate接口定义详解》Predicate是Java中的一个函数式接口,它代表一个判断逻辑,接收一个输入参数,返回一个布尔值,:本文主要介绍JavaPredicate接口的定义... 目录Java Predicate接口Java lamda表达式 Predicate<T>、BiFuncti

Spring Security基于数据库的ABAC属性权限模型实战开发教程

《SpringSecurity基于数据库的ABAC属性权限模型实战开发教程》:本文主要介绍SpringSecurity基于数据库的ABAC属性权限模型实战开发教程,本文给大家介绍的非常详细,对大... 目录1. 前言2. 权限决策依据RBACABAC综合对比3. 数据库表结构说明4. 实战开始5. MyBA

Spring Security方法级安全控制@PreAuthorize注解的灵活运用小结

《SpringSecurity方法级安全控制@PreAuthorize注解的灵活运用小结》本文将带着大家讲解@PreAuthorize注解的核心原理、SpEL表达式机制,并通过的示例代码演示如... 目录1. 前言2. @PreAuthorize 注解简介3. @PreAuthorize 核心原理解析拦截与

一文详解JavaScript中的fetch方法

《一文详解JavaScript中的fetch方法》fetch函数是一个用于在JavaScript中执行HTTP请求的现代API,它提供了一种更简洁、更强大的方式来处理网络请求,:本文主要介绍Jav... 目录前言什么是 fetch 方法基本语法简单的 GET 请求示例代码解释发送 POST 请求示例代码解释

Java图片压缩三种高效压缩方案详细解析

《Java图片压缩三种高效压缩方案详细解析》图片压缩通常涉及减少图片的尺寸缩放、调整图片的质量(针对JPEG、PNG等)、使用特定的算法来减少图片的数据量等,:本文主要介绍Java图片压缩三种高效... 目录一、基于OpenCV的智能尺寸压缩技术亮点:适用场景:二、JPEG质量参数压缩关键技术:压缩效果对比

Java调用C++动态库超详细步骤讲解(附源码)

《Java调用C++动态库超详细步骤讲解(附源码)》C语言因其高效和接近硬件的特性,时常会被用在性能要求较高或者需要直接操作硬件的场合,:本文主要介绍Java调用C++动态库的相关资料,文中通过代... 目录一、直接调用C++库第一步:动态库生成(vs2017+qt5.12.10)第二步:Java调用C++