JDK8对并发的新支持

原文:JDK8对并发的新支持

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是一种以空间换时间的策略。

CompletableFuture

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

CompletableFuture是Java 8中对Future的增强版

简单实现:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

import java.util.concurrent.CompletableFuture;

public class  implements Runnable {

CompletableFuture<Integer> re = null;

public (CompletableFuture<Integer> re) {

this.re = re;

}

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的最大改进在于,任务完成的时间也开放了出来。

1
future.complete(60);

用来设置完成时间。
CompletableFuture的异步执行:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

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的流式调用:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21
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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14
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示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

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变增加某个值

1
tryOptimisticRead()

就是刚刚所说的读写不互斥的情况。
每次读线程要读时,会先判断

1
if (!sl.validate(stamp))

validate中会先查看是否有写线程在写,然后再判断输入的值和当前的 stamp是否相同,即判断是否读线程将读到最新的数据。如果有写线程在写,或者 stamp数值不同,则返回失败。
如果判断失败,当然可以重复的尝试去读,在示例代码中,并没有让其重复尝试读,而采用的是将乐观锁退化成普通的读锁去读,这种情况就是一种悲观的读法。

1

2
stamp = sl.readLock();

StampedLock的实现思想:

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

1

2
while (pred.locked) {   

}

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

原文:大专栏  JDK8对并发的新支持

原文地址:https://www.cnblogs.com/petewell/p/11597471.html

时间: 2025-02-01 22:44:14

JDK8对并发的新支持的相关文章

MySQL 5.7新支持--------Json类型实战

1. 背景 * 在MySQL 5.7.8中,MySQL支持由RFC 7159定义的本地JSON数据类型,它支持对JSON(JavaScript对象标记)文档中的数据进行有效访问. * MySQL会对DML JSON数据自动验证.无效的DML JSON数据操作会产生错误. * 优化的存储格式.存储在JSON列中的JSON文档转换为一种内部格式,允许对Json元素进行快速读取访问. * MySQL Json类型支持建立索引增加查询性能提升. 2. Json类型所需的存储空间和值范围 类型 占用字节

Spring3.1 对Bean Validation规范的新支持(方法级别验证)

一.Bean Validation框架简介 写道 Bean Validation standardizes constraint definition, declaration and validation for the Java platform. 大体意思是:Bean Validation 标准化了Java平台的约束定义.描述.和验证. 详细了解请参考:http://beanvalidation.org/ Bean Validation现在一个有两个规范: 1.Bean Validatio

MySQL 5.7新支持--------Json索引创建实战

1. 背景 * 在MySQL 5.7.8中,MySQL支持由RFC 7159定义的本地JSON数据类型,它支持对JSON(JavaScript对象标记)文档中的数据进行有效访问. * MySQL会对DML JSON数据自动验证.无效的DML JSON数据操作会产生错误. * 优化的存储格式.存储在JSON列中的JSON文档转换为一种内部格式,允许对Json元素进行快速读取访问. * MySQL Json类型支持通过虚拟列方式建立索引,从而增加查询性能提升. 2. Json 索引 * 创建Json

使用HTML5新支持的搭建WebRtc环境来作为视频通讯

发现如果再重新设计这块的话,又会有不同的思路.对于可定位能力,我们可以全息日志采集,将每个用户在整个系统的走向异步的抓取下来,再同步到专门的日志分析系统,在这个系统中可以根据用户号码.订单号进行过滤分析.其次是防呆设计,这个在实际中很难完全避免,但是还是可以做一些工作,比如将开关和配置分级别,一些开关定义为P1,一些不重要的开关定义为P3,重要的开关只有个别人才能打开等等. 容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis).主轴的开始位置(与边框的交叉点

MySQL 5.7新支持--------通用表空间实战

1. 背景 * 一个通用的表空间是一个共享的InnoDB表空间. * 与系统表空间类似,一般的表空间是共享的表空间,可以存储多个表的数据 * 一般的表空间比文件表的表空间具有潜在的内存优势. * MySQL 将表空间元数据保存到一个表空间的生命周期中.在更少的一般表空间中,多个表对表空间元数据的内存比在单独的文件表空间表空间中的相同数量的表要少. * 一般的表空间数据文件可能放在一个相对于MySQL数据目录的目录中,它为您提供了许多文件表空间的数据文件和存储管理功能.与文件表的表空间一样,在My

java 关于多线程高并发方面

转有关的文章链接: Java 高并发一:前言: http://www.jb51.net/article/92358.htm Java 高并发二:多线程基础详细介绍 http://www.jb51.net/article/92360.htm Java 高并发三:Java内存模型和线程安全详解 http://www.jb51.net/article/92361.htm Java 高并发四:无锁详细介绍 http://www.jb51.net/article/92362.htm Java 高并发五:J

Java高并发是不是你的菜??

自从JAVA5.0增加了最初由DougLea编写的高质量的.广泛使用的.并发实用程序util.concurrent并变成了JSR-166的新包之后,在Java内置所提供的类库中,就提供了越来越多的并发编程的实用工具类.学习并掌握这些技术对于专注于Java并发编程的开发人员来讲是基本的公里,随着Java版本的不断更新与改进,开发人员可以通过Java新版本所带来的新特性,无需从头重新编写并发程序工具类. 我们该学习Java并发嘛? 我们该如何学习Java并发? CPU这么多核了,我们如何更好的利用?

JDK线程池的使用

转载自:https://my.oschina.net/hosee/blog/614319: 摘要: 本系列基于炼数成金课程,为了更好的学习,做了系列的记录. 本文主要介绍: 1. 线程池的基本使用 2. 扩展和增强线程池 3. ForkJoin 1. 线程池的基本使用 1.1.为什么需要线程池 平时的业务中,如果要使用多线程,那么我们会在业务开始前创建线程,业务结束后,销毁线程.但是对于业务来说,线程的创建和销毁是与业务本身无关的,只关心线程所执行的任务.因此希望把尽可能多的cpu用在执行任务上

JDK8新特性 Lambda表达式

一.接口的默认方法二.Lambda 表达式三.函数式接口四.方法与构造函数引用五.Lambda 作用域六.访问局部变量七.访问对象字段与静态变量八.访问接口的默认方法九.Date API十.Annotation 注解:支持多重注解 一.接口的默认方法 Java8允许我们给接口添加一个非抽象的方法实现,只需要使用 default关键字即可,这个特征又叫做扩展方法,示例如下: [java] view plain copy public interface Formula { double calcu