Java 8 中 CAS 的增强

前不久,笔者有时候地将事前写的用来测验AtomicInteger和synchronized的自增品质的代码跑了弹指间,意各市发现AtomicInteger的品质比synchronized越来越好了,经过一番原因查找,有了之类发掘:

一.从i++说起

i++这几个看是简轻巧单的操作并非叁个原子操作,它是由三步组成的。抽出i的值,举办加一操作,写会推测后的值。在三十三十二线程角逐条件下,要是两个线程同有时候调用包蕴i++的一段代码,就也许现身谬误(一个线程将另七个线程的改善覆盖的主题材料)。

当然能够运用synchronized重量级锁来开展代码块互斥访谈。但就i++这一句代码来讲还会有更轻量级的秘诀,那正是原子类AtomicInteger的getAndIncrement(卡塔尔方法。

一、CAS和synchronized适用项景

在jdk1.7中,AtomicInteger的getAndIncrement是那般的:

二.AtomicInteger

AtomicInteger坐落于java.util.concurrent.atomic包下。他提供了一种用法轻便、质量高效、线程安全地立异二个Integer变量的措施。

1、对于财富角逐比较少的情景,使用synchronized同步锁举行线程梗塞和唤醒切换甚至顾客态内核态间的切换操作额外浪费消耗cpu能源;而CAS基于硬件完成,没有须求步向幼功,无需切换线程,操作自旋概率少之甚少,因而能够得到越来越高的品质。

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);
}
2.1 AtomicInteger的用法
AtomicInteger a = new AtomicInteger;a.getAndIncrement(); a.getAndDecrement();a.compareAndSet(int expect,int update); //返回值true or falsea.getAndAdd(int delta);

2、对于能源竞争严重的情状,CAS自旋的票房价值会超大,进而浪费越来越多的CPU资源,效能低于synchronized。以java.util.concurrent.atomic包中AtomicInteger类为例,其getAndIncrement(卡塔尔方法完结如下:

而在jdk1.8中,是如此的:

2.2 原理——以getAndIncrement()为例

AtomicIntger达成的原子操作是基于Unsafe类CAS操作达成的。CAS是指Compare
And
Swap即相比并交流。只有当变量的实际值和我们传入的愿意值一致时,才实行沟通操作(用传入的新值替换旧值)。

Unsafe类提供了三种为主的CAS操作。分别是CAS更新int,CAS更新long,CAS更新Object。

/***解释一下参数含义,其它两个类似*@param var1 要更新的变量,一个Integer变量*@param var2 Integer对象中实际存储int值位置的偏移量(个人理解,欢迎指正)*@param var4 期望值*@param var5 更新为这个值*方法语义:* 如果var1这个Integer对象在其偏移var2位置的值等于var4,则将该值改为var5,否则不改。* 以上为原子操作,不会被打断,要么和期望相同修改成功,要么不同修改失败。*/public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

Unsafe类中基于基本的CAS操作又完成了getAndInt(卡塔尔(قطر‎等艺术,能够无梗塞地促成原子更新操作,不需加锁,不涉及线程堵塞等。源码如下:

//Unsafe中getAndAddInt的定义,语义是在原变量上加上var4public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { //取出现有的值 var5 = this.getIntVolatile(var1, var2); //计算处相加后的值,并进行CAS操作,如果失败说明现有值被修改,需要重新计算相加后的值。 //如果成功循环结束,返回相加操作之前的值 } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5;}

而AtomicInteger中的getAndAdd、getAndIncrement等情势就是一贯调用了Unsafe.getAndAddInt方法:

//AtomicInteger中getAndAdd的定义public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta);}//AtomicInteger getAndIncrement定义public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1);}

探访此间就可以以知道晓用AtomicInteger达成“i++”是什么成功线程安全的了。上文说道i++分为三步:取值,总括,写回。但一旦写回时,原值已经被其余线程改动了,那此番的计量就不精确了。unsafe.getAndAddInt中,通过CAS操作制止了这种气象,若是写回时值更换了就再也取值,重新总计并写回,平昔循环直到成功。

复制代码
public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

三.此外原子类的选择

java.util.concurrent.atomic包中除去AtomicIntger,还应该有好多其余原子类,它们的完成原理都得以追根求源到unsafe的七个CAS操作,这里不做详细记录了,只品种各举两个事例轻松记录一下它们的使用,详细的使用准绳,可在用届期参谋JDK
API文书档案和JDK源码

澳门新葡萄京官网首页,复制代码

能够见见,在jdk1.第88中学,直接行使了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,没有此办法。(PS:为了搜索原因,小编反编写翻译了Unsafe,发现CAS的挫败重试正是在getAndAddInt方法里完毕的,作者用反射获取到Unsafe实例,编写了跟getAndAddInt雷同的代码,但测量试验结果却跟jdk1.7的getAndIncrement同样慢,不知道Unsafe里面毕竟玩了怎么样黑法力,还请高人不吝引导)(补充:小说最后原来就有推论)

3.1 原子更新为主类型

AtomicBooleanAtomicIntegerAtomicLong

行使AtomicBoolean达成多线程下某一段代码只举办一遍。

AtomicBoolean isHappen= new AtomicBoolean;if(isHappen.compareAndSet(false,true)) { //多线程下这一段代码也只会执行一次 do something、、}

AtomicIntegerArrayAtomicLongArrayAtomicReferenceArray

AtomicIntegerArray API举例:

//初始化AtomicIntegerArray ai = new AtomicIntegerArray(new int[]{1,2,3,4,5});//以原子方式将输入值与数组中索引i的元素相加int addAndGet(int i,int delta); //如果当前值等于预期值,则以原子方式将数组位置i的元素设置成update值boolean compareAndSet(int i,int expect,int update);

AtomicReferenceAtomicStampedRferenceAtomicMarkableReference

一经compareAndSet(current,
next卡塔尔国方法成功执行,则直接重回;假使线程角逐能够,导致compareAndSet(current,
next卡塔尔(قطر‎方法平素不可能学有所成举行,则会一直循环等待,直到耗尽cpu分配给该线程的小时片,进而小幅度下落作用。

经过查看AtomicInteger的源码可以开采,受影响的还也可以有getAndAdd、addAndGet等大部分措施。

ABA问题

ABA难点是指在张开CAS操作时可能变量已经变了,並且变了多次,但调换的结果和原先的均等,即又变回开头值了。当时其实已经变过了,但CAS以为它和旧值同样,就感到还没变,其实早已变过了。

二、CAS错误的行使情况

有了本次对CAS的增长,大家又多了贰个施用非梗塞算法的理由。

AtomicStampedReference解决ABA问题

缓慢解决的艺术正是记录版本号。AtomicReference只需记下贰个T
reference代表要操作的援用。而AtomicStampedReference使用二个Pair泛型类同临时候记录reference和stamp。举办CAS操作时相比较的是其一pair。Pair中的reference和stamp都以final的,一旦初叶化就不会被涂改了。

应用AtomicStampedReference主要利用的是其compareAndSet方法.

 public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return//先判断期望值和当前值是否相同,不同也没有必要进行CAS了 expectedReference == current.reference && expectedStamp == current.stamp &&//如果新值和当前值已经一样了,也不需更新了。 ((newReference == current.reference && newStamp == current.stamp) ||//进行CAS:用新值生成新的Pair,如果实际值和传入的当前值pair是同一个,则进行替换。//底层使用unsafe的compareAndSwapObject方法比较引用类型 casPair(current, Pair.of(newReference, newStamp))); }

AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater

//可用于原子地更新User类的old字段。old字段必须是public volatile修饰的。AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class,"old");User conan = new User("conan",10);a.getAndIncrement;

Java并发之基本功知识Java并发之volatile关键字Java并发之synchronized关键字Java并发之原子类Java并发之线程池Java并发之并发工具类Java并发之AQS原理Java并发之ThreadLocal使用和源码深入分析

复制代码
1 public class CASDemo {
2 private final int THREAD_NUM = 1000;
3 private final int MAX_VALUE = 20000000;
4 private AtomicInteger casI = new AtomicInteger(0);
5 private int syncI = 0;
6 private String path =
“/Users/pingping/DataCenter/Books/Linux/Linux常用命令详明.txt”;
7
8 public void casAdd() throws InterruptedException {
9 long begin = System.currentTimeMillis();
10 Thread[] threads = new Thread[THREAD_NUM];
11 for (int i = 0; i < THREAD_NUM; i++) {
12 threads[i] = new Thread(new Runnable() {
13 public void run() {
14 while (casI.get() < MAX_VALUE) {
15 casI.getAndIncrement();
16 }
17 }
18 });
19 threads[i].start();
20 }
21 for (int j = 0; j < THREAD_NUM; j++) {
22 threads[j].join();
23 }
24 System.out.println(“CAS costs time: ” + (System.currentTimeMillis() –
begin));
25 }
26
27 public void syncAdd() throws InterruptedException {
28 long begin = System.currentTimeMillis();
29 Thread[] threads = new Thread[THREAD_NUM];
30 for (int i = 0; i < THREAD_NUM; i++) {
31 threads[i] = new Thread(new Runnable() {
32 public void run() {
33 while (syncI < MAX_VALUE) {
34 synchronized (“syncI”) {
35 ++syncI;
36 }
37 }
38 }
39 });
40 threads[i].start();
41 }
42 for (int j = 0; j < THREAD_NUM; j++)
43 threads[j].join();
44 System.out.println(“sync costs time: ” + (System.currentTimeMillis()

终极交给测量检验代码,必要在乎的是,此测量检验方法简单狂暴,compareAndSet的属性比不上synchronized,并无法差相当少地说synchronized就更好,两个的使用方法是存在差别的,况兼在实际应用中,还也许有职业处理,不容许有与此相类似高的竞争强度,此相比较仅看成一个参照,该测验能够证实的是,AtomicInteger.getAndIncrement的性质有了大幅度进级。

  • begin));
    45 }
    46 }
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(奥迪Q5卡塔尔 Core(TM卡塔尔国 i7-4710HQ CPU
@2.50GHz(四核八线程)下的测量检验结果(波动比较小,所以每项只测量检验了四伍遍,取中间八个较中间的值State of Qatar:

在自己的双核cpu上运维,结果如下:

jdk1.7

可以见到在分化的线程下,接纳CAS总结消耗的时刻远多于使用synchronized形式。原因在于第15行
14 while (casI.get() < MAX_VALUE) {
15 casI.getAndIncrement();
16 }

AtomicInteger.getAndIncrement 12,653,757,034
synchronized 4,146,813,462
AtomicInteger.compareAndSet 12,952,821,234

的操作是三个耗费时间超级少的操作,15行试行完事后会立即步入循环,继续实践,进而招致线程冲突严重。

jdk1.8

三、改善的CAS使用景况

AtomicInteger.getAndIncrement 2,159,486,620
synchronized 4,067,309,911
AtomicInteger.compareAndSet 12,893,188,541

为了解决上述难题,只要求让每三遍巡回施行的年华变长,就能够以急剧减少线程冲突。改善代码如下:

补充:应网上基友供给,在那提供Unsafe.getAndAddInt的连带源码以致本身的测验代码。

复制代码
1 public class CASDemo {
2 private final int THREAD_NUM = 1000;
3 private final int MAX_VALUE = 1000;
4 private AtomicInteger casI = new AtomicInteger(0);
5 private int syncI = 0;
6 private String path =
“/Users/pingping/DataCenter/Books/Linux/Linux常用命令详明.txt”;
7
8 public void casAdd2() throws InterruptedException {
9 long begin = System.currentTimeMillis();
10 Thread[] threads = new Thread[THREAD_NUM];
11 for (int i = 0; i < THREAD_NUM; i++) {
12 threads[i] = new Thread(new Runnable() {
13 public void run() {
14 while (casI.get() < MAX_VALUE) {
15 casI.getAndIncrement();
16 try (InputStream in = new FileInputStream(new File(path))) {
17 while (in.read() != -1);
18 } catch (IOException e) {
19 e.printStackTrace();
20 }
21 }
22 }
23 });
24 threads[i].start();
25 }
26 for (int j = 0; j < THREAD_NUM; j++)
27 threads[j].join();
28 System.out.println(“CAS Random costs time: ” +
(System.currentTimeMillis() – begin));
29 }
30
31 public void syncAdd2() throws InterruptedException {
32 long begin = System.currentTimeMillis();
33 Thread[] threads = new Thread[THREAD_NUM];
34 for (int i = 0; i < THREAD_NUM; i++) {
35 threads[i] = new Thread(new Runnable() {
36 public void run() {
37 while (syncI < MAX_VALUE) {
38 synchronized (“syncI”) {
39 ++syncI;
40 }
41 try (InputStream in = new FileInputStream(new File(path))) {
42 while (in.read() != -1);
43 } catch (IOException e) {
44 e.printStackTrace();
45 }
46 }
47 }
48 });
49 threads[i].start();
50 }
51 for (int j = 0; j < THREAD_NUM; j++)
52 threads[j].join();
53 System.out.println(“sync costs time: ” + (System.currentTimeMillis()

用jad反编写翻译jdk1.第88中学Unsafe取得的源码:

  • begin));
    54 }
    55 }
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源码:

在while循环中,扩大了二个读取文件内容的操作,该操作大致须要耗时40ms,进而得以减弱线程冲突。测量检验结果如下:

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);

可知在能源矛盾不大的情景下,选拔CAS格局和synchronized同步功能差不离。为何CAS相比synchronized未有收获更加高的脾性呢?

自家的测量检验代码(提示:假使eclipse等ide报错,这是因为使用了受限的Unsafe,能够将警告等级从error降为warning,具体百度就能够卡塔尔(قطر‎:

测量检验使用的jdk为1.7,而从jdk1.6从头,对锁的落到实处引进了大气的优化,如锁粗化(Lock
Coarsening)、锁排除(Lock Elimination)、轻量级锁(Lightweight
Locking)、趋向锁(Biased Locking)、适应性自旋(Adaptive
Spinning)等能力来压缩锁操作的支出。而里边自旋锁的原理,相仿于CAS自旋,以至比CAS自旋更为优化。具体内容请参考深刻JVM锁机制1-synchronized。

...
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;
    }
    ...
}

传送门:http://blog.csdn.net/chen77716/article/details/6618779

补偿2:对于品质升高的来头,有以下推论,虽不敢说整个确实无疑(因为未有用jvm的源码作为论据),但要么有异常的大把握的,多谢网上朋友@周
可人和@liuxinglanyue!

四、总结

Unsafe是通过极其管理的,无法明白成正规的java代码,差距在于:

1、使用CAS在线程冲突严重时,会大幅度回退程序品质;CAS只切合于线程冲突超级少的景观采纳。

在调用getAndAddInt的时候,如若系统底层协助fetch-and-add,那么它施行的就是native方法,使用的是fetch-and-add;
即使不帮助,就依据下面的所见到的getAndAddInt方法体那样,以java代码的主意去实行,使用的是compare-and-swap;

2、synchronized在jdk1.6自此,已经济体改进优化。synchronized的平底完成首要正视Lock-Free的队列,基本思路是自旋后堵塞,竞争切换后持续角逐锁,微微就义了公平性,但获得了高吞吐量。在线程冲突超少的情状下,能够博得和CAS肖似的性质;而线程矛盾严重的场所下,质量远超过CAS。

那也恰巧跟openjdk第88中学Unsafe::getAndAddInt上方的评释相切合:

// The following contain CAS-based Java implementations used on
// platforms not supporting native instructions

Unsafe的非常管理也正是本身上文所说的“黑法力”。

有关链接:

发表评论

电子邮件地址不会被公开。 必填项已用*标注