144.png

一、基本类型原子类

148.png

  • 首先,我们来用原子类实现i++的效果,可以看出,结果并不是50000
package com.lori.juc2023.juc8;

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo1 {
    public static final int SIZE = 50;
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();

        for (int i = 1; i <= SIZE; i++) {
            new Thread(()->{
                for (int j = 1; j <= 1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }

        System.out.println(Thread.currentThread().getName()+"result:::"+myNumber.atomicInteger.get());
    }
}


class MyNumber{
    //默认初始值为0
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addPlusPlus(){
        atomicInteger.getAndIncrement();
    }
}

145.png

原因是线程还没执行完,主线程就去获取了,我们再来睡5s后获取

package com.lori.juc2023.juc8;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo1 {
    public static final int SIZE = 50;
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();

        for (int i = 1; i <=SIZE; i++) {
            new Thread(()->{
                for (int j = 1; j <=1000; j++) {
                    myNumber.addPlusPlus();
                }
            },String.valueOf(i)).start();
        }

 try {
     TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}
        System.out.println(Thread.currentThread().getName()+"result:::"+myNumber.atomicInteger.get());
    }
}


class MyNumber{
    //默认初始值为0
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addPlusPlus(){
        atomicInteger.getAndIncrement();
    }
}

146.png

此时,我们已经能获得了预想的结果,可是等待时间人为控制不是很友好,也不准确。

CountDownLatch

  • CountDownLatch的作用很简单,就是一个或者一组线程在开始执行操作之前,必须要等到其他线程执行完才可以。我们举一个例子来说明,在考试的时候,老师必须要等到所有人交了试卷才可以走。此时老师就相当于等待线程,而学生就好比是执行的线程。
package com.lori.juc2023.juc8;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class AtomicDemo1 {
    public static final int SIZE = 50;
    public static void main(String[] args) {
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(SIZE);
        for (int i = 1; i <=SIZE; i++) {
            new Thread(()->{
                try {
                    for (int j = 1; j <=1000; j++) {
                        myNumber.addPlusPlus();
                    }
                } finally {
                    //每执行完一个线程就-1
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }

        try {
            //等待所有线程执行完毕再执行下面的
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()+"result:::"+myNumber.atomicInteger.get());
    }
}


class MyNumber{
    //默认初始值为0
    AtomicInteger atomicInteger = new AtomicInteger();
    public void addPlusPlus(){
        atomicInteger.getAndIncrement();
    }
}

147.png

二、数组类型原子类

149.png

package com.lori.juc2023.juc8;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class AtomicDemo2 {
    public static void main(String[] args) {
        //AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[]{1,2,3,4,5});
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            System.out.println(atomicIntegerArray.get(i));

        }

        int tmpInt = 0;
        tmpInt = atomicIntegerArray.getAndSet(0,2023);
        System.out.println(atomicIntegerArray.get(0));
        tmpInt = atomicIntegerArray.getAndIncrement(0);
        System.out.println(atomicIntegerArray.get(0));

    }
}

150.png

三、引用类型原子类

151.png

1、AtomicReference

package com.lori.juc2023.juc7;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

import java.util.concurrent.atomic.AtomicReference;

public class CASDemo2 {
    public static void main(String[] args) {
        AtomicReference<User> atomicReference=new AtomicReference<>();
        User lori = new User("lori", 18);
        User yyqx = new User("yyqx", 19);
        atomicReference.set(lori);
        System.out.println(atomicReference.compareAndSet(lori, yyqx)+"\t"+atomicReference.get().toString());
    }
}

@Getter
@ToString
@AllArgsConstructor
class User{
    String name;
    int age;
}

152.png

package com.lori.juc2023.juc7;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

import java.util.concurrent.atomic.AtomicReference;

public class CASDemo2 {
    public static void main(String[] args) {
        AtomicReference<User> atomicReference=new AtomicReference<>();
        User lori = new User("lori", 18);
        User yyqx = new User("yyqx", 19);
        atomicReference.set(lori);
        System.out.println(atomicReference.compareAndSet(lori, yyqx)+"\t"+atomicReference.get().toString());
        System.out.println(atomicReference.compareAndSet(lori, yyqx)+"\t"+atomicReference.get().toString());
    }
}

@Getter
@ToString
@AllArgsConstructor
class User{
    String name;
    int age;
}

153.png

2、AtomicStampedReference

ABA问题: CAS算法实现一个重要的前提需要取出内存中某时刻的数据并在当下时刻比较并且替换,那么在这个时间差会导致数据变化。 比如说一个线程1从内存位置V去取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作,将A值修改为了B值,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中仍然是A,预期OK,然后线程1操作成功。 尽管线程1的CAS操作成功,不代表这个过程是没有问题的。 解决ABA问题: 带版本号查询:

package com.lori.juc2023.juc7;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo4 {
    public static void main(String[] args) {
        Book book = new Book(1,"java");
        //传参1:引用,传参2:版本号
        AtomicStampedReference<Book> reference = new AtomicStampedReference<>(book,1);
        System.out.println(reference.getReference()+"\t"+reference.getStamp());  //Book(id=1, name=java)	1

        
        Book book1 = new Book(2,"mysql");
        boolean compareAndSet = reference.compareAndSet(book, book1, reference.getStamp(), reference.getStamp() + 1);
        System.out.println(compareAndSet+"\t"+reference.getReference()+"\t"+reference.getStamp());  //



        compareAndSet = reference.compareAndSet(book1, book, reference.getStamp(), reference.getStamp() + 1);
        System.out.println(compareAndSet+"\t"+reference.getReference()+"\t"+reference.getStamp());  //

    }
}


@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
    private int id;
    private String name;
}

154.png 首先就使用原子类,不使用版本号:

package com.lori.juc2023.juc7;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo5 {
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    public static void main(String[] args) {
        new Thread(()->{
           atomicInteger.compareAndSet(100,101);
           //暂停10毫秒
            try {
                TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        new Thread(()->{
            //暂停200毫秒,等线程1 执行完毕
             try {TimeUnit.MILLISECONDS.sleep(200);} catch (InterruptedException e) {throw new RuntimeException(e);}

            System.out.println("\t"+ atomicInteger.compareAndSet(100,2023)+"\t"+atomicInteger.get());
        },"t2").start();
    }
}

155.png 使用版本号原子方法

package com.lori.juc2023.juc7;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo5 {
    //参数:初始值;版本号
  static AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(100,1);
    public static void main(String[] args) {
        new Thread(()->{
            //拿到版本号
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"   首次版本号:::"+stamp);
            //暂停一段时间,确保t2拿到的版本号和t1一样
             try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}
             //第一次修改值
            stampedReference.compareAndSet(100,101,stamp,stamp+1);
            int stamp2 = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"线程1第一次修改后版本号:::"+stamp2+"\t"+stampedReference.getReference());
            //第二次修改值
            stampedReference.compareAndSet(101,100,stamp2,stamp2+1);
            int stamp3 = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"线程1第2次修改后版本号:::"+stamp3+"\t"+stampedReference.getReference());

                },"t1").start();


        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"   首次版本号:::"+stamp);
            //暂停3s确保t1线程已执行完毕,值已经变回100
             try {TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {throw new RuntimeException(e);}
            boolean b = stampedReference.compareAndSet(100, 2023, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+"线程2第1次修改后版本号:::"+stamp+"  "+b+"\t"+stampedReference.getReference());
        },"t2").start();
    }

}

156.png

3、AtomicMarkableReference

原子更新带有标记位的引用类对象,标记是否被修改过

package com.lori.juc2023.juc8;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicMarkableReference;

public class AtomicDemo3 {
    static AtomicMarkableReference<Integer> ses = new AtomicMarkableReference<>(100,false);
    public static void main(String[] args) {
           new Thread(()->{
               //获取标识
               boolean marked = ses.isMarked();
               System.out.println(Thread.currentThread().getName()+"获取mark::"+marked);
               //暂停2s,确保t2也能获取到和t1一样的标识
                try {
                    TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {throw new RuntimeException(e);}
                ses.compareAndSet(100,2023,marked,!marked);
               System.out.println(Thread.currentThread().getName()+"\t"+ses.getReference()+"修改后标识"+ses.isMarked());
           },"t1").start();


        new Thread(()->{
            //获取标识
            boolean marked = ses.isMarked();
            System.out.println(Thread.currentThread().getName()+"获取mark::"+marked);

            //暂停3s,确保t1已经执行完毕
            try {
                TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {throw new RuntimeException(e);}
            ses.compareAndSet(100,8888,marked,!marked);
            if(!"8888".equals(ses.getReference())){
                System.out.println(Thread.currentThread().getName() +"修改失败::标识已被修改为:::"+ses.isMarked());
            }else {
                System.out.println(Thread.currentThread().getName() + "\t" + ses.getReference() + "修改后标识" + ses.isMarked());
            }
        },"t2").start();
    }
}

157.png

四、对象的属性修改原子类

158.png

  • AtomicIntegerFieldUpdater:原子更新对象中int类型的字段
  • AtomicLongFieldUpdater:原子更新对象中long类型的字段
  • AtomicReferenceFieldUpdater:原子更新对象中引用类型的字段

1、使用目的

以一种线程安全的方式操作非线程安全对象内的某些字段 比例:以前医院全麻,现在都是局部麻醉

2、使用要求

  • 更新的对象属性必须使用pubilc volatile修饰符
  • 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。

3、代码

1、例一

i++

package com.lori.juc2023.juc8;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

public class AtomicDemo4 {
    public static void main(String[] args) {
        BankAccount bankAccount = new BankAccount();
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i = 0; i < 10; i++) {
               new Thread(()->{
                   try {
                       for (int j = 0; j < 1000; j++) {
                           bankAccount.transMoney(bankAccount);
                       }
                   } finally {
                    countDownLatch.countDown();
                   }
               },"t1").start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("结果::::::"+bankAccount.money);
    }
}


class BankAccount{
    String bankName = "CCB";
   public volatile int money = 0;//钱数
    AtomicIntegerFieldUpdater<BankAccount> atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");
    //不加synchronized,还要保证多线程操作的原子性
    public void transMoney(BankAccount bankAccount){
        atomicIntegerFieldUpdater.getAndIncrement(bankAccount);
    }
}

159.png

2、例二

需求 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作 要求只能被初始化一次,只有一个线程操作成功

package com.lori.juc2023.juc8;


import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

/**
 * 需求
 * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作
 * 要求只能被初始化一次,只有一个线程操作成功
 */
public class AtomicDemo5 {
    public static void main(String[] args) {

        MyVar myVar = new MyVar();

        for (int i = 0; i < 10; i++) {
               new Thread(()->{
                         myVar.init(myVar);
                       },String.valueOf(i)).start();
        }
    }
}


class MyVar{
    public volatile Boolean isInit = Boolean.FALSE;

    AtomicReferenceFieldUpdater<MyVar,Boolean> referenceFieldUpdater =
            AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");

    public void init(MyVar myVar){
        if(referenceFieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE)){
            System.out.println(Thread.currentThread().getName()+"\t"+"开始初始化********");
             try {
                 TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {throw new RuntimeException(e);}
            System.out.println(Thread.currentThread().getName()+"\t"+"完成初始化工作");

        }else {
            System.out.println(Thread.currentThread().getName()+"\t"+"已经有线程进行初始化工作********");
        }
    }

}

160.png

五、原子操作增强类

1、概述

161.png Java8推荐使用LongAdder对象,比AtomicLong性能更好,减少乐观锁的重试次数 当多个线程更新用于收集信息但不用于细粒度同步控制目的公共求和时,LongAdder通常优于AtomicLong。在低更新征用下,两个类具有相似的特征。但在高征用的情况下,LongAdder的预期吞吐量明显更高,但代价是空间的消耗。 162.png

2、代码

1、例一

package com.lori.juc2023.juc8;

import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;
import java.util.function.LongBinaryOperator;

public class AtomicDemo6 {
    public static void main(String[] args) {
        //LongAdder只能用来计算加法,且从0开始计算
        LongAdder longAdder = new LongAdder();
        longAdder.increment();
        longAdder.increment();
        longAdder.increment();
        longAdder.increment();
        longAdder.increment();
        longAdder.increment();
        System.out.println(longAdder.sum());
        System.out.println("*******************************************************");
        //使用给定的累加器函数和标识元素创建新的实例
        LongAccumulator accumulator = new LongAccumulator((x,y)-> x+ y,2);
        accumulator.accumulate(1);
        accumulator.accumulate(2);
        accumulator.accumulate(3);
        accumulator.accumulate(4);
        accumulator.accumulate(5);
        accumulator.accumulate(6);
        System.out.println(accumulator.get());
        System.out.println("*******************************************************");


        LongAccumulator accumulator1 = new LongAccumulator(new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left *  right;
            }
        },6);
        accumulator1.accumulate(2);
        accumulator1.accumulate(3);
        System.out.println(accumulator1.get());

    }
}

163.png

2、例二

package com.lori.juc2023.juc8;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.LongAccumulator;
import java.util.concurrent.atomic.LongAdder;

/**
 * 50个线程,每个线程1000w次,求总点赞数
 */
public class AtomicDemo7 {
    public static final int _1W = 10000;
    public static final int threadNumber = 50;

    public static void main(String[] args) throws InterruptedException {
        ClickNumber clickNumber = new ClickNumber();
        long start;
        long end;

        CountDownLatch countDownLatch1 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch2 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch3 = new CountDownLatch(threadNumber);
        CountDownLatch countDownLatch4 = new CountDownLatch(threadNumber);


        start = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {

                    try {
                        for (int j = 1; j <= 1000*_1W; j++) {
                        clickNumber.clickBySynchronized();
                        }
                    } finally {
                        countDownLatch1.countDown();

                }
            },String.valueOf(i)).start();
        }
        countDownLatch1.await();
        end = System.currentTimeMillis();
        System.out.println("********耗时:::" + (end - start) + " 毫秒"+"------clickBySynchronized:::"+clickNumber.num);


        System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");

        start = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {

                try {
                    for (int j = 1; j <= 1000*_1W; j++) {
                        clickNumber.clickByAtomicInteger();
                    }
                } finally {
                    countDownLatch2.countDown();

                }
            },String.valueOf(i)).start();
        }
        countDownLatch2.await();
        end = System.currentTimeMillis();
        System.out.println("********耗时:::" + (end - start) + " 毫秒"+"------clickByAtomicInteger:::"+clickNumber.num);

        System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");

        start = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {

                try {
                    for (int j = 1; j <= 1000*_1W; j++) {
                        clickNumber.clickLongAccumulator();
                    }
                } finally {
                    countDownLatch3.countDown();

                }
            },String.valueOf(i)).start();
        }
        countDownLatch3.await();
        end = System.currentTimeMillis();
        System.out.println("********耗时:::" + (end - start) + " 毫秒"+"------clickLongAccumulator:::"+clickNumber.num);


        System.out.println("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");

        start = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
            new Thread(() -> {

                try {
                    for (int j = 1; j <= 1000*_1W; j++) {
                        clickNumber.clickLongAdder();
                    }
                } finally {
                    countDownLatch4.countDown();

                }
            },String.valueOf(i)).start();
        }
        countDownLatch4.await();
        end = System.currentTimeMillis();
        System.out.println("********耗时:::" + (end - start) + " 毫秒"+"------clickLongAdder:::"+clickNumber.num);

    }
}

class ClickNumber {
    int num = 0;

    public synchronized void clickBySynchronized() {
        num++;
    }

    AtomicInteger atomicInteger = new AtomicInteger(0);

    public synchronized void clickByAtomicInteger() {
        atomicInteger.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();

    public void clickLongAdder() {
        longAdder.increment();
    }


    LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);

    public void clickLongAccumulator() {
        longAccumulator.accumulate(1);
    }
}

164.png

3、LongAdder原理

165.png

  • LongAdder的基本思路也就是分散热点,将value值分散到一个cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中额那个值进行CAS操作,这样热点就被分散了,冲突的概率就很小。如果要获取真正的long值,只要将各个槽中的变量值累加返回即可。
  • sum()会将所有Cell数组中的value和base累加作为返回值,核心思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。 166.png

4、小总结

LongAdder在无竞争的情况下,跟AtomicLong一样,对同一个base进行操作,当出现竞争关系的时候则是采用化整为零分散热点的做法,用空间换时间,用一个数组cells[],将一个value拆分进这个数组cells。多个线程需要同时对value进行操作的时候,可以对线程id进行hash得到hash值,再根据hash值映射到这个数组cells的某个下标,再对该下标所对应的值进行自增操作。当所有线程都操作完毕时,将数组cells的所有值和base都加起来作为最终的结果。 167.png

5、源码分析

LongAdder.java

    public void add(long x) {

        Cell[] as; long b, v; int m; Cell a;
//当首次首线程进入(as = cells) != null一定时false,此时走casBase方法,以cas的方式更新base值,只有当cas失败的时候,才会走到if里面
//条件1:cells不为空
//条件2:cas操作base失败,说明其他线程先一步修改了base正在出现竞争
        if ((as = cells) != null || !casBase(b = base, b + x)) {
//true:设置为无竞争 ;false表示竞争激烈,多个线程hash到同一个cell,可能要扩容
            boolean uncontended = true;
//条件1:cells为空
//条件2:cells数组小于0,应该不会出现
//条件3:当前线程所在的cell为空,说明当前线程还没有更新过cell,应该初始化一个cell
//条件4:更新当前线程所在的cell失败,说明现在竞争很激烈,多个线程hash到了同一个cell里面,应该扩容
//getProbe:方法返回的是线程中的threadLocalRandomProbe字段
//它是通过随机数生成的一个值,对于一个确定的线程这个值是固定的
            if (as == null || (m = as.length - 1) < 0 ||
                (a = as[getProbe() & m]) == null ||
                !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

as:表示cells的引用 b:表示获取的base值 v:表示期望值 m:表示cells数组的长度 a:表示当前线程命中的cell单元格

    final boolean casBase(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
    }

1、如果cells为空,尝试用CAS更新base字段,成功则退出 2、如果cells表为空,CAS更新base字段失败,出现竞争,uncontended为true,调用longAccumulate 3、如果cells表为空,但当前线程的映射槽为空,uncontended为true,调用longAccumulate 4、如果cells表为空,且前线程映射的槽为空,CAS更新cell的值,成功则返回,否则uncontended为false,调用longAccumulate

171.png

longAccumulate(x, null, uncontended);入参

long x:需要增加的值,默认是1 LongBinaryOperator fn :默认传递null wasUncontended:竞争标识,如果是false代表有竞争,只有cells初始化之后,并且当前线程CAS竞争修改失败,才会是false

Striped64中的一些变量和方法

base:类似于AtomicLong中全局的value值。在没有竞争情况下数据直接累加到base上,或者cells扩容时,也需要将数据写入到base上 collide:表示扩容意向,false一定不会扩容,true可能会扩容。 collsBusy:初始化cells或者扩容cells需要获取锁,0:表示无锁状态 1:表示其他线程已经持有了锁 casCellsBusy():通过CAS操作修改cellsBusy的值,CAS成功代表获取锁,返回true NCPU:当前计算机CPU数量,Cell数组扩容时会使用到 getprobe():获取当前线程的hash值 advanceProbe():重置当前线程的hash值

168.png

169.png

上述代码首先给当前线程分配一个hash值,然后进入 for(; ; ) 自旋,这个自旋分为三个分支 case1:cell[]数组已经初始化 case2:cell[]数组未初始化{首次新建} case3:数组正在初始化中

final void longAccumulate(long x, LongBinaryOperator fn,
                              boolean wasUncontended) {
    // 存储线程的probe值
    int h;
    // 如果getProbe()方法返回0,说明随机数未初始化
    if ((h = getProbe()) == 0) {
        // 强制初始化
        ThreadLocalRandom.current(); // force initialization
        // 重新获取probe值
        h = getProbe();
        // 都未初始化,肯定还不存在竞争激烈
        wasUncontended = true;
    }
    // 是否发生碰撞
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        // cells已经初始化过
        if ((as = cells) != null && (n = as.length) > 0) {
            // 当前线程所在的Cell未初始化
            if ((a = as[(n - 1) & h]) == null) {
                // 当前无其它线程在创建或扩容cells,也没有线程在创建Cell
                if (cellsBusy == 0) {       // Try to attach new Cell
                    // 新建一个Cell,值为当前需要增加的值
                    Cell r = new Cell(x);   // Optimistically create
                    // 再次检测cellsBusy,并尝试更新它为1
                    // 相当于当前线程加锁
                    if (cellsBusy == 0 && casCellsBusy()) {
                        // 是否创建成功
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            // 重新获取cells,并找到当前线程hash到cells数组中的位置
                            // 这里一定要重新获取cells,因为as并不在锁定范围内
                            // 有可能已经扩容了,这里要重新获取
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                // 把上面新建的Cell放在cells的j位置处
                                rs[j] = r;
                                // 创建成功
                                created = true;
                            }
                        } finally {
                            // 相当于释放锁
                            cellsBusy = 0;
                        }
                        // 创建成功了就返回
                        // 值已经放在新建的Cell里面了
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                // 标记当前未出现冲突
                collide = false;
            }
            // 当前线程所在的Cell不为空,且更新失败了
            // 这里简单地设为true,相当于简单地自旋一次
            // 通过下面的语句修改线程的probe再重新尝试
            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash
            // 再次尝试CAS更新当前线程所在Cell的值,如果成功了就返回
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                         fn.applyAsLong(v, x))))
                break;
            // 如果cells数组的长度达到了CPU核心数,或者cells扩容了
            // 设置collide为false并通过下面的语句修改线程的probe再重新尝试
            else if (n >= NCPU || cells != as)
                collide = false;            // At max size or stale
            // 上上个elseif都更新失败了,且上个条件不成立,说明出现冲突了
            else if (!collide)
                collide = true;
            // 明确出现冲突了,尝试占有锁,并扩容
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    // 检查是否有其它线程已经扩容过了
                    if (cells == as) {      // Expand table unless stale
                        // 新数组为原数组的两倍
                        Cell[] rs = new Cell[n << 1];
                        // 把旧数组元素拷贝到新数组中
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        // 重新赋值cells为新数组
                        cells = rs;
                    }
                } finally {
                    // 释放锁
                    cellsBusy = 0;
                }
                // 已解决冲突
                collide = false;
                // 使用扩容后的新数组重新尝试
                continue;                   // Retry with expanded table
            }
            // 更新失败或者达到了CPU核心数,重新生成probe,并重试
            h = advanceProbe(h);
        }
        // 未初始化过cells数组,尝试占有锁并初始化cells数组
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            // 是否初始化成功
            boolean init = false;
            try {                           // Initialize table
                // 检测是否有其它线程初始化过
                if (cells == as) {
                    // 新建一个大小为2的Cell数组
                    Cell[] rs = new Cell[2];
                    // 找到当前线程hash到数组中的位置并创建其对应的Cell
                    rs[h & 1] = new Cell(x);
                    // 赋值给cells数组
                    cells = rs;
                    // 初始化成功
                    init = true;
                }
            } finally {
                // 释放锁
                cellsBusy = 0;
            }
            // 初始化成功直接返回
            // 因为增加的值已经同时创建到Cell中了
            if (init)
                break;
        }
        // 如果有其它线程在初始化cells数组中,就尝试更新base
        // 如果成功了就返回
        else if (casBase(v = base, ((fn == null) ? v + x :
                                    fn.applyAsLong(v, x))))
            break;                          // Fall back on using base
    }
}

简单总结

  • 有cell数组, 新建cell对象, 然后存入cell数组
  • 有cell数组, 但是出现冲突, cell数组需要扩容 -粗体 没有cell数组, 初始化cell数组
  • 以上过程都需要锁, 但是当前线程获取不到锁, 直接更新base值 172.png

sum()

  • sum()会将所有cell数组中的value和base累加返回作为返回值
  • 核心思想就是将之前AtomicLong一个value的更新压力分到多个value中,从而降级更新热点。
  • sum执行时,并没有限制对base 和cells的更新(一句要命的话)。所以LongAdder不是强一致性的,它是最终一致性的。
  • 首先,最终返回的sum局部变量,初始被复制为base,而最终返回时,很可能base己经被更新了,而此时局部变量sum不会更新,造成不一致。
  • 其次,这里对cell的读取也无法保证是最后一次写入的值。所以,sum方法在没有并发的情况下,可以获得正确的结果。

6、AtomicLong与LongAdder的对比

AtomicLong:

  • 线程安全,可允许一些性能损耗,要求精度高的时候可以使用。保证精度,性能代价。AtomicLong是多个线程针对单个热点值value进行原子操作。
  • 原理:CAS+自旋
  • 为什么自旋会成为瓶颈:N个线程CAS操作修改线程的值,每次只有一个成功过,其它N-1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。

LongAdder:

  • 原理:CAS+Base+Cells数组分散
  • 场景:高并发下全局计算
  • 缺陷:sum求和后还有计算线程修改结果的话,最后的结果不够准确