Java 高并发十: JDK8对并发的新支持详解

 更新时间:2016年09月12日 09:20:08   作者:Hosee  
本文主要介绍Java 高并发JDK8的支持,这里整理了详细的资料及1. LongAdder 2. CompletableFuture 3. StampedLock的介绍,有兴趣的小伙伴可以参考下

1. LongAdder

和AtomicLong类似的使用方式,但是性能比AtomicLong更好。

LongAdder与AtomicLong都是使用了原子操作来提高性能。但是LongAdder在AtomicLong的基础上进行了热点分离,热点分离类似于有锁操作中的减小锁粒度,将一个锁分离成若干个锁来提高性能。在无锁中,也可以用类似的方式来增加CAS的成功率,从而提高性能。

LongAdder原理图:

AtomicLong的实现方式是内部有个value 变量,当多线程并发自增,自减时,均通过CAS 指令从机器指令级别操作保证并发的原子性。唯一会制约AtomicLong高效的原因是高并发,高并发意味着CAS的失败几率更高, 重试次数更多,越多线程重试,CAS失败几率又越高,变成恶性循环,AtomicLong效率降低。

而LongAdder将把一个value拆分成若干cell,把所有cell加起来,就是value。所以对LongAdder进行加减操作,只需要对不同的cell来操作,不同的线程对不同的cell进行CAS操作,CAS的成功率当然高了(试想一下3+2+1=6,一个线程3+1,另一个线程2+1,最后是8,LongAdder没有乘法除法的API)。

可是在并发数不是很高的情况,拆分成若干的cell,还需要维护cell和求和,效率不如AtomicLong的实现。LongAdder用了巧妙的办法来解决了这个问题。

初始情况,LongAdder与AtomicLong是相同的,只有在CAS失败时,才会将value拆分成cell,每失败一次,都会增加cell的数量,这样在低并发时,同样高效,在高并发时,这种“自适应”的处理方式,达到一定cell数量后,CAS将不会失败,效率大大提高。

LongAdder是一种以空间换时间的策略。

2. CompletableFuture

实现CompletionStage接口(40余个方法),大多数方法多数应用在函数式编程中。并且支持流式调用

CompletableFuture是Java 8中对Future的增强版

简单实现:

import java.util.concurrent.CompletableFuture;

public class AskThread implements Runnable {
 CompletableFuture<Integer> re = null;

 public AskThread(CompletableFuture<Integer> re) {
 this.re = re;
 }

 @Override
 public void run() {
 int myRe = 0;
 try {
 myRe = re.get() * re.get();
 } catch (Exception e) {
 }
 System.out.println(myRe);
 }

 public static void main(String[] args) throws InterruptedException {
 final CompletableFuture<Integer> future = new CompletableFuture<Integer>();
 new Thread(new AskThread(future)).start();
 // 模拟长时间的计算过程
 Thread.sleep(1000);
 // 告知完成结果
 future.complete(60);
 }
}

Future最令人诟病的就是要等待,要自己去检查任务是否完成了,在Future中,任务完成的时间是不可控的。而 CompletableFuture的最大改进在于,任务完成的时间也开放了出来。

future.complete(60);

用来设置完成时间。

CompletableFuture的异步执行:

public static Integer calc(Integer para) {
 try {
 // 模拟一个长时间的执行
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 }
 return para * para;
 }

 public static void main(String[] args) throws InterruptedException,
 ExecutionException {
 final CompletableFuture<Integer> future = CompletableFuture
 .supplyAsync(() -> calc(50));
 System.out.println(future.get());
 }
CompletableFuture的流式调用:

public static Integer calc(Integer para) {
 try {
 // 模拟一个长时间的执行
 Thread.sleep(1000);
 } catch (InterruptedException e) {
 }
 return para * para;
 }

 public static void main(String[] args) throws InterruptedException,
 ExecutionException {
 CompletableFuture<Void> fu = CompletableFuture
 .supplyAsync(() -> calc(50))
 .thenApply((i) -> Integer.toString(i))
 .thenApply((str) -> "\"" + str + "\"")
 .thenAccept(System.out::println);
 fu.get();
 }

组合多个CompletableFuture:

public static Integer calc(Integer para) {
 return para / 2;
 }

 public static void main(String[] args) throws InterruptedException,
 ExecutionException {
 CompletableFuture<Void> fu = CompletableFuture
 .supplyAsync(() -> calc(50))
 .thenCompose(
  (i) -> CompletableFuture.supplyAsync(() -> calc(i)))
 .thenApply((str) -> "\"" + str + "\"")
 .thenAccept(System.out::println);
 fu.get();
 }

这几个例子更多是侧重Java8的一些新特性,这里就简单举下例子来说明特性,就不深究了。
CompletableFuture跟性能上关系不大,更多的是为了支持函数式编程,在功能上的增强。当然开放了完成时间的设置是一大亮点。

3. StampedLock

在上一篇中刚刚提到了锁分离,而锁分离的重要的实现就是ReadWriteLock。而StampedLock则是ReadWriteLock的一个改进。StampedLock与ReadWriteLock的区别在于,StampedLock认为读不应阻塞写,StampedLock认为当读写互斥的时候,读应该是重读,而不是不让写线程写。这样的设计解决了读多写少时,使用ReadWriteLock会产生写线程饥饿现象。

所以StampedLock是一种偏向于写线程的改进。

StampedLock示例:

import java.util.concurrent.locks.StampedLock;

public class Point {
 private double x, y;
 private final StampedLock sl = new StampedLock();

 void move(double deltaX, double deltaY) { // an exclusively locked method
 long stamp = sl.writeLock();
 try {
 x += deltaX;
 y += deltaY;
 } finally {
 sl.unlockWrite(stamp);
 }
 }

 double distanceFromOrigin() { // A read-only method
 long stamp = sl.tryOptimisticRead();
 double currentX = x, currentY = y;
 if (!sl.validate(stamp)) {
 stamp = sl.readLock();
 try {
 currentX = x;
 currentY = y;
 } finally {
 sl.unlockRead(stamp);
 }
 }
 return Math.sqrt(currentX * currentX + currentY * currentY);
 }
}

上述代码模拟了写线程和读线程, StampedLock根据stamp来查看是否互斥,写一次stamp变增加某个值

tryOptimisticRead()

就是刚刚所说的读写不互斥的情况。

每次读线程要读时,会先判断

if (!sl.validate(stamp))

validate中会先查看是否有写线程在写,然后再判断输入的值和当前的 stamp是否相同,即判断是否读线程将读到最新的数据。

如果有写线程在写,或者 stamp数值不同,则返回失败。

如果判断失败,当然可以重复的尝试去读,在示例代码中,并没有让其重复尝试读,而采用的是将乐观锁退化成普通的读锁去读,这种情况就是一种悲观的读法。

stamp = sl.readLock();

StampedLock的实现思想:

CLH自旋锁:当锁申请失败时,不会立即将读线程挂起,在锁当中会维护一个等待线程队列,所有申请锁,但是没有成功的线程都记录在这个队列中。每一个节点(一个节点代表一个线程),保存一个标记位(locked),用于判断当前线程是否已经释放锁。当一个线程试图获得锁时,取得当前等待队列的尾部节点作为其前序节点。并使用类似如下代码判断前序节点是否已经成功释放锁

while (pred.locked) {  
}

这个循环就是不断等前面那个结点释放锁,这样的自旋使得当前线程不会被操作系统挂起,从而提高了性能。
当然不会进行无休止的自旋,会在若干次自旋后挂起线程。

相关文章

  • Java集合ArrayList与LinkedList详解

    Java集合ArrayList与LinkedList详解

    这篇文章主要介绍了Java集合ArrayList与LinkedList详解,对于ArrayList和LinkedList,他们都是List接口的一个实现类,并且我们知道他们的实现方式各不相同,例如ArrayList底层实现是一个数组
    2022-08-08
  • RabbitMQ 3.9.7 镜像模式集群与Springboot 2.5.5 整合

    RabbitMQ 3.9.7 镜像模式集群与Springboot 2.5.5 整合

    今天我们来聊聊 RabbitMQ 3.9.7 镜像模式集群与Springboot 2.5.5 整合,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧
    2021-10-10
  • Java中实现代码优化的技巧分享

    Java中实现代码优化的技巧分享

    这篇文章主要跟大家谈谈优化这个话题,那么我们一起聊聊Java中如何实现代码优化这个问题,小编这里有几个实用的小技巧分享给大家,需要的可以参考一下
    2022-08-08
  • Spring MVC登录注册以及转换json数据

    Spring MVC登录注册以及转换json数据

    本文主要介绍了Spring MVC登录注册以及转换json数据的相关知识。具有很好的参考价值。下面跟着小编一起来看下吧
    2017-04-04
  • Sentinel热点规则示例详解分析

    Sentinel热点规则示例详解分析

    这篇文章主要介绍了何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制
    2021-09-09
  • java读取文件内容为string字符串的方法

    java读取文件内容为string字符串的方法

    今天小编就为大家分享一篇java读取文件内容为string字符串的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-07-07
  • Java class文件格式之特殊字符串_动力节点Java学院整理

    Java class文件格式之特殊字符串_动力节点Java学院整理

    特殊字符串出现在class文件中的常量池中,本着循序渐进和减少跨度的原则, 首先把class文件中的特殊字符串做一个详细的介绍, 然后再回过头来继续讲解常量池,对java class 文件格式相关知识感兴趣的的朋友一起学习吧
    2017-06-06
  • Java调用第三方http接口的常用方式总结

    Java调用第三方http接口的常用方式总结

    这篇文章主要介绍了Java调用第三方http接口的常用方式总结,具有很好的参考价值,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-06-06
  • 详解Java中NullPointerException异常的原因详解以及解决方法

    详解Java中NullPointerException异常的原因详解以及解决方法

    这篇文章主要介绍了详解Java中NullPointerException异常的原因详解以及解决方法。文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-08-08
  • java实现在性能测试中进行业务验证实例

    java实现在性能测试中进行业务验证实例

    这篇文章主要为大家介绍了java实现在性能测试中进行业务验证实例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-07-07

最新评论