Java 8 中 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中,没有此方法。(PS:为了找出原因,我反编译了Unsafe,发现CAS的失败重试就是在getAndAddInt方法里完成的,我用反射获取到Unsafe实例,编写了跟getAndAddInt相同的代码,但测试结果却跟jdk1.7的getAndIncrement一样慢,不知道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; 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
补充:应网友要求,在此提供Unsafe.getAndAddInt的相关源码以及我的测试代码。
用jad反编译jdk1.8中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);
我的测试代码(提示:如果eclipse等ide报错,那是因为使用了受限的Unsafe,可以将警告级别从error降为warning,具体百度即可):
... 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; } ... }
补充2:对于性能提升的原因,有以下推论,虽不敢说百分之百正确(因为没有用jvm的源码作为论据),但还是有很大把握的,感谢网友@周 可人和@liuxinglanyue!
Unsafe是经过特殊处理的,不能理解成常规的java代码,区别在于:
在调用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
Unsafe的特殊处理也就是我上文所说的“黑魔法”。
相关链接:
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
Java 8 中 CAS 的增强的更多相关文章
- java8中CAS的增强
注:ifeve.com的同名文章为本人所发,此文在其基础做了些调整.转载请注明出处! 一.java8中CAS的增强 前些天,我偶然地将之前写的用来测试AtomicInteger和synchronize ...
- Java 17中对switch的模式匹配增强
还记得Java 16中的instanceof增强 吗? 通过下面这个例子再回忆一下: Map<String, Object> data = new HashMap<>(); d ...
- CAS在Java类中的应用
CAS 这个指令全称 compare and swap 即比较替换指令,在现代处理器新加入的指令.指导思想:基于乐观锁机制.比较一个变量在内存值中的值和变量的当前值(旧值).如果相等,则认为该变量没有 ...
- Java原子类中CAS的底层实现
Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...
- Java 14中对switch的增强,终于可以不写break了
面对这样的if语句,你是不是很难受呢? if (flag == 1) { log.info("didispace.com: 1"); } else if (flag == 2) { ...
- JAVA面试中问及HIBERNATE与 MYBATIS的对比,在这里做一下总结
我是一名java开发人员,hibernate以及mybatis都有过学习,在java面试中也被提及问道过,在项目实践中也应用过,现在对hibernate和mybatis做一下对比,便于大家更好的理解和 ...
- java项目中可能会使用到的jar包解释
一.Struts2 用的版本是struts2.3.1.1 一个简单的Struts项目所需的jar包有如下8个 1. struts2-core-2.3.1.1.jar: Struts2的核心类库. 2. ...
- Java 8 中的 Streams API 详解
为什么需要 Stream Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全不同的概念.它也不同于 StAX 对 ...
- JAVA面试中问及HIBERNATE与 MYBATIS的对比,在这里做一下总结(转)
hibernate以及mybatis都有过学习,在java面试中也被提及问道过,在项目实践中也应用过,现在对hibernate和mybatis做一下对比,便于大家更好的理解和学习,使自己在做项目中更加 ...
随机推荐
- ASP.NET 4.5.256 has not been registered on the Web server
请见:http://answers.microsoft.com/en-us/insider/forum/insider_apps-insider_other/aspnet-45256-has-not- ...
- iOS中关于KVC与KVO知识点
iOS中关于KVC与KVO知识点 iOS中关于KVC与KVO知识点 一.简介 KVC/KVO是观察者模式的一种实现,在Cocoa中是以被万物之源NSObject类实现的NSKeyValueCodin ...
- android应用崩溃的调试方法(c++ lib so文件库崩溃)
android调试工具addr2line使用: 1.将ndk中的arm-linux-androideabi-addr2line可执行文件的路径加入配置文件~/.bashrc中,例如: export P ...
- R语言学习笔记:向量化
R语言最强大的方面之一就是函数的向量化,这些函数可以直接对向量的每个元素进行操作.例如: 对每个元素进行开方 > v<-c(4,3,8,16,7.3) > v [1] 4.0 3 ...
- poj 3007 Organize Your Train part II(二叉排序树)
题目:http://poj.org/problem?id=3007 题意:按照图示的改变字符串,问有多少种..字符串.. 思路:分几种排序的方法,,刚开始用map 超时(map效率不高啊..),后来搜 ...
- C语言中的malloc和free
最近在研究php自定义函数的实现,其中php自定义函数在传递参数时,是放到人为的一个栈中,这个跟写C程序时,参数入栈的这个栈还不一样,其中延伸到了 malloc 以及free 有人说在free(p)后 ...
- Java 简单登录MVC
构建一个简单的基于MVC模式的JavaWeb 零晨三点半了,刚刚几个兄弟一起出去吼歌,才回来,这应该是我大学第二次去K歌,第一次是大一吧,之后每次兄弟喊我,我都不想去,因为我还是很害怕去KTV,或许是 ...
- Android使用Webview加载网页
安卓使用Webview来加载和显示网页内容,首先在layout文件中定义Webview <?xml version="1.0" encoding="utf-8&qu ...
- Ext入门学习系列(五)表格控件(2)
上节学习了Ext中表格控件,从创建,到定义数据源.绑定显示,大体明白了一个基本的表格控件是怎么实现的.而我们用表格控件多用于从各种数据源接收数据并显示出来,并不是写死的.本章我们就不同数据源的不同实现 ...
- undo日志
InnoDB’s Undo 前言 Undo log是InnoDB MVCC事务特性的重要组成部分.当我们对记录做了变更操作时就会产生undo记录,Undo记录默认被记录到系统表空间(ibdata)中, ...