1.原子操作类有哪些

2.根本类型原子类

3.数组类型原子类

4.援用类型原子类

5.对象的属性批改原子类

6.LongAdder原理剖析

7.LongAdder源码解读

8.总结

1.原子操作类有哪些
JAVA并发编程——CAS概念以及ABA问题咱们通过以前这篇博客,基础性地理解了一下CAS的概念以及ABA的用法,而且应用了一下根本的AtomicInteger类,对原子援用类有了一个初步的理解。明天咱们来零碎归类一下原子援用类:

2.根本类型原子类
首先是根本类型原子类:

这是咱们之前的博客介绍过的,咱们就列举一下咱们常常应用的api:

public final int get() //获取以后的值public final int getAndSet(int newValue)//获取以后的值,并设置新的值,caspublic final int getAndIncrement()//获取以后的值,并自增,caspublic final int getAndDecrement() //获取以后的值,并自减,caspublic final int getAndAdd(int delta) //获取以后的值,并加上预期的值boolean compareAndSet(int expect, int update) //如果输出的数值等于预期值,则以原子形式将该值设置为输出值(update)

无非就是应用了cas,对数据的操作进行了原子性校验,这样多线程操作数据的时候就不会失落操作了。

3.数组类型原子类

这一组api也比较简单

 AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);//创立数组atomicIntegerArray.getAndSet(0,1122);atomicIntegerArray.getAndIncrement(1);
public class AtomicIntegerArrayDemo {    public static void main(String[] args) {        //初始化一个数组        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(5);        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});        //打印一下看看原始值        for(int i =0;i<atomicIntegerArray.length();i++){            System.out.println(atomicIntegerArray.get(i));        }        System.out.println();        System.out.println();        System.out.println();        int tmpInt = 0;        //把下标为0的值换成1122        tmpInt = atomicIntegerArray.getAndSet(0, 1122);        //获取下标为0的值        System.out.println(tmpInt + "\t" + atomicIntegerArray.get(0));        //将下标为1的值进行+1        atomicIntegerArray.getAndIncrement(1);        atomicIntegerArray.getAndIncrement(1);        tmpInt = atomicIntegerArray.getAndIncrement(1);        System.out.println(tmpInt + "\t" + atomicIntegerArray.get(1));    }}

4.援用类型原子类

这个类,咱们在 JAVA并发编程——CAS概念以及ABA问题里介绍过,就是将这个值设置一个版本号,能够解决ABA问题。

5.对象的属性批改原子类


这个类比拟有意思,咱们来介绍一下:

咱们应用这几个类的目标就是要以一种线程平安的形式,操作非线程安全类的某些字段:比方操作一个Account(账户类)中的Balance(余额)字段,因为其它字段比方username(用户名)不容易更改,咱们只须要对Balance进行加锁就行了。

留神:
1)更新的对象属性必须应用 public volatile 修饰符。
2)应用静态方法newUpdater()创立一个更新器,并且须要设置想要更新的类和属性。

@Datapublic class BankAccount {    private volatile int money;    public void transferMoney(BankAccount bankAccount) {        //创立一个更新器        AtomicIntegerFieldUpdater<BankAccount> integerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");        integerFieldUpdater.incrementAndGet(bankAccount);    }}
public class AtomicIntegerFieldUpdaterDemo {    public static void main(String[] args) {        BankAccount bankAccount = new BankAccount();        for (int i = 1; i<=1000;i++){            int finalI = i;            new Thread(() -> {                bankAccount.transferMoney(bankAccount);            }, String.valueOf(i)).start();        }        //暂停毫秒        try {            TimeUnit.MILLISECONDS.sleep(2000);        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println(bankAccount.getMoney());    }}

6.LongAdder原理剖析
接下来介绍一下最重要的一个类:LongAdder!

咱们都晓得,如果在并发的状况下,做一个点赞计数器,能够应用AtomicInteger,它采纳自旋的乐观锁形式,进行自增,然而如果并发量更大一点,就会让很多线程取抢占AtomicInteger,造成线程空转,这个时候,咱们能够应用LongAdder!

LongAdder的性能比AtomicInteger高出很多,咱们能够先来比照着看一下这两个类的性能:

import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicLong;import java.util.concurrent.atomic.LongAccumulator;import java.util.concurrent.atomic.LongAdder;public class LongAdderDemo {    static class ClickNumberNet {        int number = 0;        //应用synchronized锁进行自增        public synchronized void clickBySync() {            number++;        }        AtomicLong atomicLong = new AtomicLong(0);        //应用原子类进行自增        public void clickByAtomicLong() {            atomicLong.incrementAndGet();        }        LongAdder longAdder = new LongAdder();        //应用longAdder进行自增        public void clickByLongAdder() {            longAdder.increment();        }        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);        //应用longAccumulator(自定义自增函数内容)进行自增        public void clickByLongAccumulator() {            longAccumulator.accumulate(1);        }    }    public static void main(String[] args) throws InterruptedException {        ClickNumberNet clickNumberNet = new ClickNumberNet();        long startTime;        long endTime;        CountDownLatch countDownLatch = new CountDownLatch(50);        CountDownLatch countDownLatch2 = new CountDownLatch(50);        CountDownLatch countDownLatch3 = new CountDownLatch(50);        CountDownLatch countDownLatch4 = new CountDownLatch(50);        startTime = System.currentTimeMillis();        for (int i = 1; i <= 50; i++) {            new Thread(() -> {                try {                    for (int j = 1; j <= 100 * 10000; j++) {                        clickNumberNet.clickBySync();                    }                } finally {                    countDownLatch.countDown();                }            }, String.valueOf(i)).start();        }        countDownLatch.await();        endTime = System.currentTimeMillis();        System.out.println("synchronized----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickBySync result: " + clickNumberNet.number);        startTime = System.currentTimeMillis();        for (int i = 1; i <= 50; i++) {            new Thread(() -> {                try {                    for (int j = 1; j <= 100 * 10000; j++) {                        clickNumberNet.clickByAtomicLong();                    }                } finally {                    countDownLatch2.countDown();                }            }, String.valueOf(i)).start();        }        countDownLatch2.await();        endTime = System.currentTimeMillis();        System.out.println("AtomicInteger----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByAtomicLong result: " + clickNumberNet.atomicLong);        startTime = System.currentTimeMillis();        for (int i = 1; i <= 50; i++) {            new Thread(() -> {                try {                    for (int j = 1; j <= 100 * 10000; j++) {                        clickNumberNet.clickByLongAdder();                    }                } finally {                    countDownLatch3.countDown();                }            }, String.valueOf(i)).start();        }        countDownLatch3.await();        endTime = System.currentTimeMillis();        System.out.println("LongAdder----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAdder result: " + clickNumberNet.longAdder.sum());        startTime = System.currentTimeMillis();        for (int i = 1; i <= 50; i++) {            new Thread(() -> {                try {                    for (int j = 1; j <= 100 * 10000; j++) {                        clickNumberNet.clickByLongAccumulator();                    }                } finally {                    countDownLatch4.countDown();                }            }, String.valueOf(i)).start();        }        countDownLatch4.await();        endTime = System.currentTimeMillis();        System.out.println("LongAccumulator----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAccumulator result: " + clickNumberNet.longAccumulator.longValue());    }}

运行后果为:

咱们发现,LongAdder的性能是AtomicInteger类性能的十倍!咱们来看一下为什么LongAdder能这么快。

咱们从源码的形容中能够看出:当应用少的并发来操作这个LongAdder类的时候,LongAdder和AtomicLong领有差不多的性能,当并发量大的时候,LongAdder的性能会高很多,因为LongAdder破费了更多的空间。

也就是说,LongAdder采取的是用空间换工夫的办法来晋升了统计速度。
咱们顺便来看一下LongAdder的父类Striped64:

Striped64用了一个Cell数组和base以及cellsBusy,咱们来看一下上面这个形容和这张原理图,咱们就能大略明确LongAdder的底层原理了:

LongAdder的基本思路就是扩散热点将value值扩散到一个Cell数组中,不同线程会命中到数组的不同槽位外面,各个线程对本人槽中的那个值进行CAS操作,这样热点就被扩散了,抵触的概率就会小很多,如果要获取真正的long值,只有将各个槽的变量累计加回就能够了。


sum()办法会将所有cell数组中的value和base累加作为返回值,核心思想就是将AtomicLong的一个value的更新压力分到多个value当中去,以空间换工夫,从而降级更新热点。

数学表白公式:

7.LongAdder源码解读

说了这么多,咱们来看看LongAdder的源码:

    public void add(long x) {        //cs示意cell的援用 ,b示意base的值,v示意期望值,m示意cell数组的长度,c示意命中cell的单元格        Cell[] cs; long b, v; int m; Cell c;        //首次操作线程,cell必定为空,去走cas,失败的时候才会走到if外面        //如果cell不为空,则必定产生过竞争强烈的景象,进入if        //如果cas失败,则竞争必定也强烈,进入if        if ((cs = cells) != null || !casBase(b = base, b + x)) {            //不是竞争强烈的 true代表无竞争,false代表竞争积攒            boolean uncontended = true;            //1.如果cell数组为空,则要进入初始化,进入办法(初始化cell数组)            //2.如果数组长度小于0,则进入初始化(初始化cell数组)(个别不会进入这个条件)            //3.getProbe()是通过算法返回这个线程应该落在哪个槽,如果这个槽还没初始化,则进入这个办法,初始化cell           //4.如果这个槽cas失败,则进入这个办法(去扩容)            if (cs == null || (m = cs.length - 1) < 0 ||                (c = cs[getProbe() & m]) == null ||                !(uncontended = c.cas(v = c.value, v + x)))                longAccumulate(x, null, uncontended);        }    }
 final void longAccumulate(long x, LongBinaryOperator fn,                              boolean wasUncontended) {        int h;        //如果线程还未随机调配哈希值,给调配一个        if ((h = getProbe()) == 0) {            ThreadLocalRandom.current(); // force initialization            h = getProbe();            wasUncontended = true;        }        boolean collide = false;                // True if last slot nonempty        done: for (;;) { //自旋            Cell[] cs; Cell c; int n; long v;            if ((cs = cells) != null && (n = cs.length) > 0) {                if ((c = cs[(n - 1) & h]) == null) {//如果命中了一个槽位,这个槽位还未初始化                    if (cellsBusy == 0) {       // Try to attach new Cell                        Cell r = new Cell(x);   // Optimistically create                        if (cellsBusy == 0 && casCellsBusy()) {//双重查看锁                            try {               // Recheck under lock                                Cell[] rs; int m, j;                                if ((rs = cells) != null &&                                    (m = rs.length) > 0 &&                                    rs[j = (m - 1) & h] == null) {                                    rs[j] = r;                                    break done;                                }                            } finally {                                cellsBusy = 0;                            }                            continue;           // Slot is now non-empty                        }                    }                    collide = false;                }                else if (!wasUncontended)       // CAS already known to fail                    wasUncontended = true;      // Continue after rehash                else if (c.cas(v = c.value,                               (fn == null) ? v + x : fn.applyAsLong(v, x)))//对某一个槽位进行cas                    break;                else if (n >= NCPU || cells != cs)//容量曾经超过CPU核数,不能扩容                    collide = false;            // At max size or stale                else if (!collide)                    collide = true;                else if (cellsBusy == 0 && casCellsBusy()) {//扩容操作                    try {                        if (cells == cs)        // Expand table unless stale                            cells = Arrays.copyOf(cs, n << 1);                    } finally {                        cellsBusy = 0;                    }                    collide = false;                    continue;                   // Retry with expanded table                }                h = advanceProbe(h);            }            else if (cellsBusy == 0 && cells == cs && casCellsBusy()) {//初始化cell数组                try {                           // Initialize table                    if (cells == cs) {                        Cell[] rs = new Cell[2];                        rs[h & 1] = new Cell(x);                        cells = rs;                        break done;                    }                } finally {                    cellsBusy = 0;                }            }            // Fall back on using base            //如果全副操作都失败,进行casBase,兜底操作             else if (casBase(v = base,                             (fn == null) ? v + x : fn.applyAsLong(v, x)))                break done;        }    }

8.总结

明天咱们学习了原子操作类以及LongAdder源码剖析,
根本类型原子类咱们平时最相熟,应用乐观锁。
数组类型原子类能够操作数组。
援用类型原子类带版本号,能够解决aba问题。
对象的属性批改原子类,能够让加锁的粒度更细。
LongAdder是每个线程领有本人的槽,各个线程个别只对本人槽中的那个值进行CAS操作。