java cas 性能_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中,没有此方法。(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

d0c1501a6d8bb921cf36400dc89de69f.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在多线程编程CAS(Compare and Swap)机制被广泛使用。它可以实现无锁并发,提高程序的性能。但是,CAS 机制存在 ABA 问题,即当一个值从 A 变为 B,再从 B 变回 A,这时另一个线程也会执行相同的操作,而我们无法区分这两次操作是否真正修改了值。为了解决这个问题,Java 提供了一个原子类 AtomicStampedReference。 AtomicStampedReference 可以保证在进行 CAS 操作时,不仅比较对象值是否相等,还会比较对象的时间戳是否相等。时间戳是一个整数值,每次对象值的改变都会导致时间戳的变化。因此,即使对象值从 A 变为 B,再从 B 变回 A,时间戳也会发生变化,从而避免了 ABA 问题的出现。 下面是一个使用 AtomicStampedReference 解决 ABA 问题的示例代码: ```java import java.util.concurrent.atomic.AtomicStampedReference; public class AtomicStampedReferenceDemo { static AtomicStampedReference<Integer> reference = new AtomicStampedReference<>(1, 0); public static void main(String[] args) { new Thread(() -> { int stamp = reference.getStamp(); System.out.println(Thread.currentThread().getName() + " 第 1 次版本号:" + stamp); reference.compareAndSet(1, 2, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + " 第 2 次版本号:" + reference.getStamp()); reference.compareAndSet(2, 1, reference.getStamp(), reference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + " 第 3 次版本号:" + reference.getStamp()); }, "线程 1").start(); new Thread(() -> { int stamp = reference.getStamp(); System.out.println(Thread.currentThread().getName() + " 第 1 次版本号:" + stamp); // 等待线程 1 完成 CAS 操作 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } boolean isSuccess = reference.compareAndSet(1, 3, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + " 是否修改成功:" + isSuccess); System.out.println(Thread.currentThread().getName() + " 当前版本号:" + reference.getStamp()); System.out.println(Thread.currentThread().getName() + " 当前值:" + reference.getReference()); }, "线程 2").start(); } } ``` 输出结果: ``` 线程 1 第 1 次版本号:0 线程 1 第 2 次版本号:1 线程 1 第 3 次版本号:2 线程 2 第 1 次版本号:0 线程 2 是否修改成功:false 线程 2 当前版本号:2 线程 2 当前值:1 ``` 通过输出结果可以看出,线程 2 尝试将值从 1 改为 3,但是由于版本号已经被线程 1 修改过了,因此 CAS 操作失败,避免了 ABA 问题的出现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值