java8中CAS的增强
注:ifeve.com的同名文章为本人所发,此文在其基础做了些调整。转载请注明出处!
一、java8中CAS的增强
前些天,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因查找,有了如下发现:
在jdk1.7中,AtomicInteger的getAndIncrement是这样的:
- public final int getAndIncrement() {
- for (;;) {
- int current = get();
- int next = current + 1;
- if (compareAndSet(current, next))
- return current;
- }
- }
- public final boolean compareAndSet(int expect, int update) {
- return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
- }
而在jdk1.8中,是这样的:
- public final int getAndIncrement() {
- return unsafe.getAndAddInt(this, valueOffset, 1);
- }
可以看出,在jdk1.8中,直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此方法。基本可以断定,Unsafe新增的方法是性能提升的关键。(文章末尾将附上一些探索的过程及推论)
通过查看AtomicInteger的源码可以发现,受影响的还有getAndAdd、addAndGet等大部分方法。
结论:有了这次对CAS的增强,我们又多了一个使用非阻塞算法的理由。
二、测试方法
以下给出测试代码,供参考与测试。需要注意的是,此测试方法简单粗暴,compareAndSet的性能不如synchronized,并不能简单地说synchronized就更好,两者的使用方式是存在差异的,而且在实际使用中,还有业务处理,不可能有如此高的竞争强度,此对比仅作为一个参考,该测试能够证明的是,AtomicInteger.getAndIncrement的性能有了大幅提升。
- package performance;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.atomic.AtomicInteger;
- import java.util.concurrent.locks.LockSupport;
- /**
- * @author trytocatch@163.com
- */
- public class AtomicTest {
- //测试规模,调用一次getAndIncreaseX视作提供一次业务服务,记录提供TEST_SIZE次服务的耗时
- private static final int TEST_SIZE = 100000000;
- //客户线程数
- private static final int THREAD_COUNT = 10;
- //使用CountDownLatch让各线程同时开始
- private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1);
- private int n = 0;
- private AtomicInteger ai = new AtomicInteger(0);
- private long startTime;
- public void init() {
- startTime = System.nanoTime();
- }
- /**
- * 使用AtomicInteger.getAndIncrement,测试结果为1.8比1.7有明显性能提升
- * @return
- */
- private final int getAndIncreaseA() {
- int result = ai.getAndIncrement();
- if (result == TEST_SIZE) {
- System.out.println(System.nanoTime() - startTime);
- System.exit(0);
- }
- return result;
- }
- /**
- * 使用synchronized来完成同步,测试结果为1.7和1.8几乎无性能差别
- * @return
- */
- private final int getAndIncreaseB() {
- int result;
- synchronized (this) {
- result = n++;
- }
- if (result == TEST_SIZE) {
- System.out.println(System.nanoTime() - startTime);
- System.exit(0);
- }
- return result;
- }
- /**
- * 使用AtomicInteger.compareAndSet在java代码层面做失败重试(与1.7的AtomicInteger.getAndIncrement的实现类似),
- * 测试结果为1.7和1.8几乎无性能差别
- * @return
- */
- private final int getAndIncreaseC() {
- int result;
- do {
- result = ai.get();
- } while (!ai.compareAndSet(result, result + 1));
- if (result == TEST_SIZE) {
- System.out.println(System.nanoTime() - startTime);
- System.exit(0);
- }
- return result;
- }
- public class MyTask implements Runnable {
- @Override
- public void run() {
- cdl.countDown();
- try {
- cdl.await();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- while (true)
- getAndIncreaseA();// getAndIncreaseB();
- }
- }
- public static void main(String[] args) throws InterruptedException {
- AtomicTest at = new AtomicTest();
- for (int n = 0; n < THREAD_COUNT; n++)
- new Thread(at.new MyTask()).start();
- System.out.println("start");
- at.init();
- at.cdl.countDown();
- }
- }
以下是在Intel(R) Core(TM) i7-4710HQ CPU @2.50GHz(四核八线程)下的测试结果(波动较小,所以每项只测试了四五次,取其中一个较中间的值):
- jdk1.7
- AtomicInteger.getAndIncrement 12,653,757,034
- synchronized 4,146,813,462
- AtomicInteger.compareAndSet 12,952,821,234
- jdk1.8
- AtomicInteger.getAndIncrement 2,159,486,620
- synchronized 4,067,309,911
- AtomicInteger.compareAndSet 12,893,188,541
三、提升原因的探索及推论
一开始,我怀疑在1.8中,Unsafe直接使用了native方法,而1.7是在getAndIncrement里完成的失败重试,也就是在java代码层面,所以造成了性能的差别,于是我用jad反编译了Unsafe,得到了如下代码:
- public final int getAndAddInt(Object obj, long l, int i)
- {
- int j;
- do
- j = getIntVolatile(obj, l);
- while(!compareAndSwapInt(obj, l, j, j + i));
- return j;
- }
- public native int getIntVolatile(Object obj, long l);
- public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);
并且参考了openjdk8的Unsafe源码:
- public final int getAndAddInt(Object o, long offset, int delta) {
- int v;
- do {
- v = getIntVolatile(o, offset);
- } while (!compareAndSwapInt(o, offset, v, v + delta));
- return v;
- }
- public native int getIntVolatile(Object o, long offset);
- public final native boolean compareAndSwapInt(Object o, long offset,
- int expected,
- int x);
由上面的信息可以看出,1.8中,失败重试也是在java代码层面进行的(区别是转移到了Unsafe的java方法里面),算是推翻了我的猜测,于是我决定通过反射,直接获取到Unsafe实例,编写跟Unsafe.getAndAddInt方法一样的代码来测试,看能否找到一些新的线索:
- ...
- import sun.misc.Unsafe;
- public class AtomicTest {
- ....
- private Unsafe unsafe;
- private long valueOffset;
- public AtomicTest(){
- Field f;
- try {
- f = Unsafe.class.getDeclaredField("theUnsafe");
- f.setAccessible(true);
- unsafe = (Unsafe)f.get(null);
- valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
- }catch(NoSuchFieldException e){
- ...
- }
- }
- private final int getAndIncreaseD(){
- int result;
- do{
- result = unsafe.getIntVolatile(ai, valueOffset);
- }while(!unsafe.compareAndSwapInt(ai, valueOffset, result, result+1));
- if(result == MAX){
- System.out.println(System.nanoTime()-startTime);
- System.exit(0);
- }
- return result;
- }
- ...
- }
但让人失望的是,该方式跟1.7的getAndIncrement效率一样,明明跟1.8的Unsafe.getAndAddInt方法一样,却是截然不同的效率。
最后,经过ifeve.com的网友们的指点,对性能的提升原因有了如下推论,虽不敢说百分之百正确(因为没有用jvm的源码作为论据),但还是有很大把握的,感谢网友@周 可人和@liuxinglanyue!
Unsafe是经过特殊处理的,不能理解成常规的java代码,区别在于:
- 1.8在调用getAndAddInt的时候,如果系统底层支持fetch-and-add,那么它执行的就是native方法,使用的是fetch-and-add;
- 如果不支持,就按照上面的所看到的getAndAddInt方法体那样,以java代码的方式去执行,使用的是compare-and-swap;
这也正好跟openjdk8中Unsafe::getAndAddInt上方的注释相吻合:
- // The following contain CAS-based Java implementations used on
- // platforms not supporting native instructions
- // The following contain CAS-based Java implementations used on
相关链接:
http://ashkrit.blogspot.com/2014/02/atomicinteger-java-7-vs-java-8.html
http://hg.openjdk.java.net/jdk8u/hs-dev/jdk/file/a006fa0a9e8f/src/share/classes/sun/misc/Unsafe.java
java8中CAS的增强的更多相关文章
- Java 8 中 CAS 的增强
几天前,我偶然地将之前写的用来测试AtomicInteger和synchronized的自增性能的代码跑了一下,意外地发现AtomicInteger的性能比synchronized更好了,经过一番原因 ...
- 谈一谈Java8的函数式编程(二) --Java8中的流
流与集合 众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...
- Java8 中 ConcurrentHashMap工作原理的要点分析
简介: 本文主要介绍Java8中的并发容器ConcurrentHashMap的工作原理,和其它文章不同的是,本文重点分析了不同线程的各类并发操作如get,put,remove之间是如何同步的,以及这些 ...
- Java7 和 Java8 中的 ConcurrentHashMap 原理解析
Java7 中 ConcurrentHashMap ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些. 整个 ConcurrentHash ...
- java7,java8 中HashMap和ConcurrentHashMap简介
一:Java7 中的HashMap 结构: HashMap 里面是一个数组,然后数组中每个元素是一个单向链表.链表中每个元素称为一个Entry 实例,Entry 包含四个属性:key, value, ...
- Java8函数之旅 (二) --Java8中的流
流与集合 众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...
- Java7与Java8中的HashMap和ConcurrentHashMap知识点总结
JAVA7 Java7的ConcurrentHashMap里有多把锁,每一把锁用于其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率呢.这 ...
- Java8中的Stream API
本篇文章继续介绍Java 8的另一个新特性——Stream API.新增的Stream API与InputStream和OutputStream是完全不同的概念,Stream API是对Java中集合 ...
- java8中map的meger方法的使用
java8中map有一个merge方法使用示例: /** * 打印出包含号码集的label的集合 * * @param args */ public static void main(String[] ...
随机推荐
- zookeeper 节点的移动与删除
package com.zhengmo.test; import java.util.List; import org.apache.zookeeper.CreateMode; import org. ...
- $.getJSON在IE8下失效
$.getJSON("/Home/GetData?r=" + Math.random(), { ids: ids }, function(data) { //处理逻辑 }); 原因 ...
- ARCGIS多种影像裁剪
在互联网上下载的遥感影像都进行过分幅处理,下载下来的影像多是规则的四方形,而在进行遥感影像研究时,多是针对特定区域来进行,比如研究北京市的遥感影像,不在北京市范围内的影像对于研究者就没有利用意义,如果 ...
- activemq的几种基本通信方式总结
简介 在前面一篇文章里讨论过几种应用系统集成的方式,发现实际上面向消息队列的集成方案算是一个总体比较合理的选择.这里,我们先针对具体的一个消息队列Activemq的基本通信方式进行探讨.activem ...
- centos 7 开启端口重启防火墙
开启端口 firewall-cmd --zone=public --add-port=80/tcp --permanent 命令含义: --zone #作用域 --add-port=8 ...
- 【转】如何在Mac系统中安装R的rattle包
[转自知乎]:https://www.zhihu.com/question/28944497 1. 安装 xquartz (http://xquartz.macosforge.org)2. 安装 GT ...
- yii + elasticsearch 手册
https://zhuowenji1.gitbooks.io/elasticsearch/content/an_zhuang_yii2.html
- C++:名字查找先于类型检查
Sub-Title: Name Hiding. "In C++, there is no overloading across scopes - derived class scopes a ...
- Ubuntu 14.04下搭建 Android 开发环境(1) -JDK安装
1.下载最新的jdk安装,地址:http://www.oracle.com/technetwork/java/javase/downloads/ 2.解压jdk-8u20-linux-x64.gz,我 ...
- 微信支付-“申请退款”接口遇到curl出错,错误码:58
该错误是没有 使用证书 导致的. 解决办法: 1. 下载证书. 2. 拷贝到 WxPayPubHelper/cacert/目录下 重试退款,OK