JUC并发编程与源码分析(4)

一、CAS

1.1 没有CAS之前

多线程环境不使用原子类保证线程安全

public class T3
{
    
    
    volatile int number = 0;
    //读取
    public int getNumber()
    {
    
    
        return number;
    }
    //写入加锁保证原子性
    public synchronized void setNumber()
    {
    
    
        number++;
    }
}

多线程环境使用原子类保证线程安全(基本数据类型)

public class T3
{
    
    
    volatile int number = 0;
    //读取
    public int getNumber()
    {
    
    
        return number;
    }
    //写入加锁保证原子性
    public synchronized void setNumber()
    {
    
    
        number++;
    }
    //=================================
    AtomicInteger atomicInteger = new AtomicInteger();

    public int getAtomicInteger()
    {
    
    
        return atomicInteger.get();
    }

    public void setAtomicInteger()
    {
    
    
        atomicInteger.getAndIncrement();
    }
}

1.2 是什么?

compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数——内存位置、预期原值及更新值。
执行CAS操作的时候,将内存位置的值与预期原值比较:
如果相匹配,那么处理器会自动将该位置值更新为新值,
如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功

原理

CAS有3个操作数,位置内存值V,旧的预期值A,要修改的更新值B。
当且仅当旧的预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做或重来
在这里插入图片描述

硬件级别保证

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。
它是非阻塞的且自身原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。

CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。
执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现的, 其实在这一点上还是有排他锁的,只是比起用synchronized, 这里的排他时间要短的多, 所以在多线程情况下性能会比较好

CASDemo代码

public class CASDemo
{
    
    
    public static void main(String[] args) throws InterruptedException
    {
    
    
        AtomicInteger atomicInteger = new AtomicInteger(5);

        System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 1024)+"\t"+atomicInteger.get());
    }
}

在这里插入图片描述

源码分析compareAndSet(int expect,int update)

compareAndSet()方法的源代码:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
var1:表示要操作的对象
var2:表示要操作对象中属性地址的偏移量
var4:表示需要修改数据的期望的值
var5/var6:表示需要修改为的新值

1.3 CAS底层原理(对Unsafe的理解)

Unsafe

在这里插入图片描述

1 Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中CAS操作的执行依赖于Unsafe类的方法。
注意Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

2 变量valueOffset,表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
在这里插入图片描述
3 变量value用volatile修饰,保证了多线程之间的内存可见性。

i++是线程不安全的,那atomicInteger.getAndIncrement()

CAS的全称为Compare-And-Swap,它是一条CPU并发原语
它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
在这里插入图片描述
CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。
在这里插入图片描述

总结

在多线程环境下,多个线程同时操作一个volatile变量,会出现写丢的情况,但是使用了cas之后,在修改失败之后会循环进行写操作,知道写成功为止。

底层汇编

Unsafe类中的compareAndSwapInt,是一个本地方法,该方法的实现位于unsafe.cpp中

在这里插入图片描述

cmpxchg

// 调用 Atomic 中的函数 cmpxchg来进行比较交换,其中参数x是即将更新的值,参数e是原内存的值
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;

在这里插入图片描述
win10
在这里插入图片描述

总结

CAS是靠硬件实现的从而在硬件层面提升效率,最底层还是交给硬件来保证原子性和可见性
实现方式是基于硬件平台的汇编指令,在intel的CPU中(X86机器上),使用的是汇编指令cmpxchg指令。

核心思想就是:比较要更新变量的值V和预期值E(compare),相等才会将V的值设为新值N(swap)如果不相等自旋再来。

1.4 自旋锁,借鉴CAS思想

自旋锁(spinlock)

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁
当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU
在这里插入图片描述
在这里插入图片描述

手写一个自旋锁

/**
 * 题目:实现一个自旋锁
 * 自旋锁好处:循环比较获取没有类似wait的阻塞。
 *
 * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现
 * 当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到。
 */
public class SpinLockDemo {
    
    
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void lock() {
    
    
        System.out.println(Thread.currentThread().getName() + " come in");
        while (!atomicReference.compareAndSet(null, Thread.currentThread())) {
    
    

        }
        System.out.println(Thread.currentThread().getName() + "获取锁成功");
    }

    public void unlock() {
    
    
        atomicReference.compareAndSet(Thread.currentThread(), null);
        System.out.println(Thread.currentThread().getName() + "释放锁成功");
    }

    public static void main(String[] args) {
    
    
        SpinLockDemo demo = new SpinLockDemo();
        new Thread(() -> {
    
    
            demo.lock();
            try {
    
    
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            demo.unlock();
        }, "t1").start();

        new Thread(() -> {
    
    
            demo.lock();
            demo.unlock();
        }, "t2").start();
    }
}

1.5 CAS缺点

  • ABA问题
  • 循环时间长开销大
  • 只能保证一个共享变量的原子操作

ABAdemo

public class ABADemo {
    
    
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    
    public static void main(String[] args) {
    
    
        abaProblem();
    }

    public static void abaProblem() {
    
    
        new Thread(() -> {
    
    
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        //暂停毫秒
        try {
    
     TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) {
    
     e.printStackTrace(); }

        new Thread(() -> {
    
    
            boolean b = atomicInteger.compareAndSet(100, 20210308);
            System.out.println(Thread.currentThread().getName()+"\t"+"修改成功否:"+b+"\t"+atomicInteger.get());
        },"t2").start();
    }
}

解决方案

使用版本号。变量前面追加版本号,每次变量更新的时候把版本号加1,那么A->B->A就会变成1A->2B->3A。从Java1.5开始,JDK的Atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法的作用是首先检查当前引用是否等于预期引用,并且检查当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

在这里插入图片描述

public class ABADemo {
    
    
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"---默认版本号: "+stamp);
            //让后面的t4获得和t3一样的版本号,都是1,好比较
            try {
    
     TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
    
     e.printStackTrace(); }

            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"---1次版本号: "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"---2次版本号: "+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
    
    
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"---默认版本号: "+stamp);
            //上前面的t3完成ABA问题
            try {
    
     TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+"---操作成功否:"+result+"\t"+atomicStampedReference.getStamp()+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }
}

在这里插入图片描述

二、原子操作类

2.1 是什么?

在这里插入图片描述
在这里插入图片描述

2.2 分类

基本类型原子类

  • AtomicInteger
  • AtomicBoolean
  • AtomicLong

case

  • 使用countDownLatch等待50个线程分别对原子类AtomicInteger对象进行1000次+1操作后获取值
class MyNumber
{
    
    
    @Getter
    private AtomicInteger atomicInteger = new AtomicInteger();
    public void addPlusPlus()
    {
    
    
        atomicInteger.incrementAndGet();
    }
}

public class AtomicIntegerDemo
{
    
    
    public static void main(String[] args) throws InterruptedException
    {
    
    
        MyNumber myNumber = new MyNumber();
        CountDownLatch countDownLatch = new CountDownLatch(100);

        for (int i = 1; i <=100; i++) {
    
    
            new Thread(() -> {
    
    
                try
                {
    
    
                    for (int j = 1; j <=5000; j++)
                    {
    
    
                        myNumber.addPlusPlus();
                    }
                }finally {
    
    
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }

        countDownLatch.await();

        System.out.println(myNumber.getAtomicInteger().get());
    }
}
 

数组类型原子类

  • AtomicIntegerArray
  • AtomicLongArray
  • AtomicReferenceArray

case

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;

        tmpInt = atomicIntegerArray.getAndSet(0,1122);
        System.out.println(tmpInt+"\t"+atomicIntegerArray.get(0));
        atomicIntegerArray.getAndIncrement(1);
        atomicIntegerArray.getAndIncrement(1);
        tmpInt = atomicIntegerArray.getAndIncrement(1);
        System.out.println(tmpInt+"\t"+atomicIntegerArray.get(1));
    }
}
 
 

在这里插入图片描述

引用类型原子类

  • AtomicReference
  • AtomicStampedReference
  • AtomicMarkableReference

AtomicReference

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

public class AtomicReferenceDemo
{
    
    
    public static void main(String[] args)
    {
    
    
        User z3 = new User("z3",24);
        User li4 = new User("li4",26);

        AtomicReference<User> atomicReferenceUser = new AtomicReference<>();

        atomicReferenceUser.set(z3);
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
        System.out.println(atomicReferenceUser.compareAndSet(z3,li4)+"\t"+atomicReferenceUser.get().toString());
    }
}

在这里插入图片描述

AtomicStampedReference

  • 携带版本号的引用类型原子类,可以解决ABA问题
  • 解决修改过几次
  • 状态戳原子引用
public class ABADemo {
    
    
    static AtomicInteger atomicInteger = new AtomicInteger(100);
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100,1);

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"---默认版本号: "+stamp);
            //让后面的t4获得和t3一样的版本号,都是1,好比较
            try {
    
     TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
    
     e.printStackTrace(); }

            atomicStampedReference.compareAndSet(100,101,stamp,stamp+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"---1次版本号: "+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t"+"---2次版本号: "+atomicStampedReference.getStamp());
        },"t3").start();

        new Thread(() -> {
    
    
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t"+"---默认版本号: "+stamp);
            //上前面的t3完成ABA问题
            try {
    
     TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
            boolean result = atomicStampedReference.compareAndSet(100, 20210308, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t"+"---操作成功否:"+result+"\t"+atomicStampedReference.getStamp()+"\t"+atomicStampedReference.getReference());
        },"t4").start();
    }

    public static void abaProblem() {
    
    
        new Thread(() -> {
    
    
            atomicInteger.compareAndSet(100,101);
            atomicInteger.compareAndSet(101,100);
        },"t1").start();

        //暂停毫秒
        try {
    
     TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) {
    
     e.printStackTrace(); }

        new Thread(() -> {
    
    
            boolean b = atomicInteger.compareAndSet(100, 20210308);
            System.out.println(Thread.currentThread().getName()+"\t"+"修改成功否:"+b+"\t"+atomicInteger.get());
        },"t2").start();
    }
}

在这里插入图片描述

AtomicMarkableReference

  • 原子更新带有标记位的引用类型对象
  • 解决是否修改过
  • 状态戳(true/false)原子引用
public class AtomicMarkableReferenceDemo
{
    
    
    static AtomicMarkableReference atomicMarkableReference = new AtomicMarkableReference(100,false);

    public static void main(String[] args)
    {
    
    
        new Thread(() -> {
    
    
            boolean marked = atomicMarkableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t"+"---默认修改标识:"+marked);
            try {
    
     TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
            atomicMarkableReference.compareAndSet(100,101,marked,!marked);
        },"t1").start();

        new Thread(() -> {
    
    
            boolean marked = atomicMarkableReference.isMarked();
            System.out.println(Thread.currentThread().getName()+"\t"+"---默认修改标识:"+marked);
            try {
    
     TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
            boolean b = atomicMarkableReference.compareAndSet(100, 20210308, marked, !marked);

            System.out.println(Thread.currentThread().getName()+"\t"+"---操作是否成功:"+b);
            System.out.println(Thread.currentThread().getName()+"\t"+atomicMarkableReference.getReference());
            System.out.println(Thread.currentThread().getName()+"\t"+atomicMarkableReference.isMarked());

        },"t2").start();
    }
}

在这里插入图片描述

字段修改原子类

  • AtomicIntegerFieldUpdater: 原子更新对象中int类型字段的值
  • AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
  • AtomicReferencefieldUpdater:原子更新引用类型字段的值

以上几个类会原子更新引用类型字段的值

使用目的:
以一种线程安全的方式操作非线程安全对象内的某些字段
使用要求:

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

AtomicIntegerFieldUpdater

假如有一个账户表,里面只有余额字段money经常需要变化,那么我们就可以使用字段修改原子类来加锁这个字段进行修改。
下面测试案例使用1000个线程对账户余额进行加1操作

class BankAccount {
    
    
    String bankName = "ccb";

    //以一种线程安全的方式操作非线程安全对象内的某些字段

    //1 更新的对象属性必须使用 public volatile 修饰符。
    public volatile int money = 0;

    //2 因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须
    // 使用静态方法newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。
    AtomicIntegerFieldUpdater FieldUpdater = AtomicIntegerFieldUpdater.newUpdater(BankAccount.class,"money");

    public void transfer(BankAccount bankAccount)
    {
    
    
        FieldUpdater.incrementAndGet(bankAccount);
    }
}

public class AtomicIntegerFieldUpdaterDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        BankAccount bankAccount = new BankAccount();

        for (int i = 1; i <=1000; i++) {
    
    
            new Thread(() -> {
    
    
                bankAccount.transfer(bankAccount);
            },String.valueOf(i)).start();
        }

        //暂停几秒钟线程
        try {
    
     TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
    
     e.printStackTrace(); }


        System.out.println(Thread.currentThread().getName()+"\t"+"---bankAccount: "+bankAccount.money);
    }
}

在这里插入图片描述

AtomicReferenceFieldUpdater

  • 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
class MyVar
{
    
    
    public volatile Boolean isInit = Boolean.FALSE;
    AtomicReferenceFieldUpdater<MyVar,Boolean> FieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class,Boolean.class,"isInit");

    public void init(MyVar myVar)
    {
    
    
        if(FieldUpdater.compareAndSet(myVar,Boolean.FALSE,Boolean.TRUE))
        {
    
    
            System.out.println(Thread.currentThread().getName()+"\t"+"---start init");
            try {
    
     TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
    
     e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"---end init");
        }else{
    
    
            System.out.println(Thread.currentThread().getName()+"\t"+"---抢夺失败,已经有线程在修改中");
        }
    }

}


/**
 *  多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,要求只能初始化一次
 */
public class AtomicReferenceFieldUpdaterDemo
{
    
    
    public static void main(String[] args)
    {
    
    
        MyVar myVar = new MyVar();

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

在这里插入图片描述

原子操作增强类原理深度解析

  • DoubleAccumulator
  • DoubleAdder
  • LongAccumulator
  • LongAdder
    在这里插入图片描述

问题?

  1. 热点商品点赞计算器,点赞数加加统计,不要求实时精确
  2. 一个很大的List,里面都是int类型,如何实现加加,说说思路

点赞计数器

  • LongAdder
    在这里插入图片描述
  • LongAccumulator
    在这里插入图片描述
入门
  • LongAdder只能用来计算加法,且从零开始计算
  • LongAccumulator提供了自定义的函数操作
public class LongAdderAPIDemo {
    
    
    public static void main(String[] args) {
    
    
        LongAdder longAdder = new LongAdder();//只能做加法

        longAdder.increment();
        longAdder.increment();
        longAdder.increment();

        System.out.println(longAdder.longValue());

        LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() {
    
    
            @Override
            public long applyAsLong(long left, long right)
            {
    
    
                return left - right;
            }
        }, 100);

        longAccumulator.accumulate(1);//1
        longAccumulator.accumulate(2);//3
        longAccumulator.accumulate(3);//6

        System.out.println(longAccumulator.longValue());
        
    }
}

在这里插入图片描述

LongAdder高性能对比
  • 50个线程,每个线程100W次,对比Synchronized,AtomicInteger,AtomicLong,LongAdder,LongAccumulator所耗费的时间。
class ClickNumber
{
    
    
    int number = 0;
    public synchronized void add_Synchronized()
    {
    
    
        number++;
    }

    AtomicInteger atomicInteger = new AtomicInteger();
    public void add_AtomicInteger()
    {
    
    
        atomicInteger.incrementAndGet();
    }

    AtomicLong atomicLong = new AtomicLong();
    public void add_AtomicLong()
    {
    
    
        atomicLong.incrementAndGet();
    }

    LongAdder longAdder = new LongAdder();
    public void add_LongAdder()
    {
    
    
        longAdder.increment();
        //longAdder.sum();
    }

    LongAccumulator longAccumulator = new LongAccumulator((x,y) -> x+y,0);
    public void add_LongAccumulator()
    {
    
    
        longAccumulator.accumulate(1);
    }

}

/**
 *  50个线程,每个线程100W次,总点赞数出来
 */
public class LongAdderCalcDemo
{
    
    
    public static final int SIZE_THREAD = 50;
    public static final int _1W = 10000;

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

        CountDownLatch countDownLatch1 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch2 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch3 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch4 = new CountDownLatch(SIZE_THREAD);
        CountDownLatch countDownLatch5 = new CountDownLatch(SIZE_THREAD);
        //========================

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
    
    
            new Thread(() -> {
    
    
                try
                {
    
    
                    for (int j = 1; j <=100 * _1W; j++) {
    
    
                        clickNumber.add_Synchronized();
                    }
                }catch (Exception e){
    
    
                    e.printStackTrace();
                }finally {
    
    
                    countDownLatch1.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch1.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_Synchronized"+"\t"+clickNumber.number);


        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
    
    
            new Thread(() -> {
    
    
                try
                {
    
    
                    for (int j = 1; j <=100 * _1W; j++) {
    
    
                        clickNumber.add_AtomicInteger();
                    }
                }catch (Exception e){
    
    
                    e.printStackTrace();
                }finally {
    
    
                    countDownLatch2.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch2.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_AtomicInteger"+"\t"+clickNumber.atomicInteger.get());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
    
    
            new Thread(() -> {
    
    
                try
                {
    
    
                    for (int j = 1; j <=100 * _1W; j++) {
    
    
                        clickNumber.add_AtomicLong();
                    }
                }catch (Exception e){
    
    
                    e.printStackTrace();
                }finally {
    
    
                    countDownLatch3.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_AtomicLong"+"\t"+clickNumber.atomicLong.get());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
    
    
            new Thread(() -> {
    
    
                try
                {
    
    
                    for (int j = 1; j <=100 * _1W; j++) {
    
    
                        clickNumber.add_LongAdder();
                    }
                }catch (Exception e){
    
    
                    e.printStackTrace();
                }finally {
    
    
                    countDownLatch4.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_LongAdder"+"\t"+clickNumber.longAdder.longValue());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <=SIZE_THREAD; i++) {
    
    
            new Thread(() -> {
    
    
                try
                {
    
    
                    for (int j = 1; j <=100 * _1W; j++) {
    
    
                        clickNumber.add_LongAccumulator();
                    }
                }catch (Exception e){
    
    
                    e.printStackTrace();
                }finally {
    
    
                    countDownLatch5.countDown();
                }
            },String.valueOf(i)).start();
        }
        countDownLatch5.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: "+(endTime - startTime) +" 毫秒"+"\t add_LongAccumulator"+"\t"+clickNumber.longAccumulator.longValue());
    }
}

在这里插入图片描述
性能对比显而易见。

源码、原理分析

在这里插入图片描述

  • LongAdder是Striped64的子类

    在这里插入图片描述
原理(LongAdder为什么这么快?)
官网说明

在这里插入图片描述
当多个线程更新用于收集统计信息等目的的公共值时,此类通常比 AtomicLong 更可取,而不是用于细粒度同步控制。 在低更新竞争下,这两个类具有相似的特征。 但在高争用情况下,此类的预期吞吐量明显更高,但代价是更高的空间消耗。

Striped64

Striped64比较重要的几个成员变量

/** Number of CPUS, to place bound on table size        CPU数量,即cells数组的最大长度 */
static final int NCPU = Runtime.getRuntime().availableProcessors();

/**
 * Table of cells. When non-null, size is a power of 2.
cells数组,为2的幂,2,4,8,16.....,方便以后位运算
 */
transient volatile Cell[] cells;

/**基础value值,当并发较低时,只累加该值主要用于没有竞争的情况,通过CAS更新。
 * Base value, used mainly when there is no contention, but also as
 * a fallback during table initialization races. Updated via CAS.
 */
transient volatile long base;

/**创建或者扩容Cells数组时使用的自旋锁变量调整单元格大小(扩容),创建单元格时使用的锁。
 * Spinlock (locked via CAS) used when resizing and/or creating Cells. 
 */
transient volatile int cellsBusy;

Striped64中一些变量或者方法的定义

在这里插入图片描述

Cell
  • 是是 java.util.concurrent.atomic 下 Striped64 的一个内部类
    在这里插入图片描述
LongAdder为什么这么快?

LongAdder的基本思路就是分散热点,将value值分散到一个Cell数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的那个值进行CAS操作,这样热点就被分散了,冲突的概率就小很多。如果要获取真正的long值,只要将各个槽中的变量值累加返回。

sum()会将所有Cell数组中的value和base累加作为返回值,核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,
从而降级更新热点

在这里插入图片描述
数学表达:

  • 内部有一个base变量,一个Cell[]数组。
    • base变量:非竞态条件下,直接累加到该变量上
    • Cell[]数组:竞态条件下,累加个各个线程自己的槽Cell[i]中
      在这里插入图片描述
小总结

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

在这里插入图片描述
在这里插入图片描述

源码分析
  • longAdder.increment()
    在这里插入图片描述
add(1L)

在这里插入图片描述

	public void add(long x) {
    
    
        Cell[] as; long b, v; int m; Cell a;
        if ((as = cells) != null || !casBase(b = base, b + x)) {
    
    
            boolean uncontended = true;
            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);
        }
    }

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

	/**
     * Handles cases of updates involving initialization, resizing,
     * creating new Cells, and/or contention. See above for
     * explanation. This method suffers the usual non-modularity
     * problems of optimistic retry code, relying on rechecked sets of
     * reads.
     *
     * @param x the value
     * @param fn the update function, or null for add (this convention
     * avoids the need for an extra field or function in LongAdder).
     * @param wasUncontended false if CAS failed before call
     */
    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
        for (;;) {
    
    
            Cell[] as; Cell a; int n; long v;
            if ((as = cells) != null && (n = as.length) > 0) {
    
    
                if ((a = as[(n - 1) & h]) == null) {
    
    
                    if (cellsBusy == 0) {
    
           // Try to attach new Cell
                        Cell r = new Cell(x);   // Optimistically create
                        if (cellsBusy == 0 && casCellsBusy()) {
    
    
                            boolean created = false;
                            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;
                                    created = true;
                                }
                            } finally {
    
    
                                cellsBusy = 0;
                            }
                            if (created)
                                break;
                            continue;           // Slot is now non-empty
                        }
                    }
                    collide = false;
                }
                else if (!wasUncontended)       // CAS already known to fail
                    wasUncontended = true;      // Continue after rehash
                else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                             fn.applyAsLong(v, x))))
                    break;
                else if (n >= NCPU || cells != as)
                    collide = false;            // At max size or stale
                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 = rs;
                        }
                    } finally {
    
    
                        cellsBusy = 0;
                    }
                    collide = false;
                    continue;                   // Retry with expanded table
                }
                h = advanceProbe(h);
            }
            else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
    
    
                boolean init = false;
                try {
    
                               // Initialize table
                    if (cells == as) {
    
    
                        Cell[] rs = new Cell[2];
                        rs[h & 1] = new Cell(x);
                        cells = rs;
                        init = true;
                    }
                } finally {
    
    
                    cellsBusy = 0;
                }
                if (init)
                    break;
            }
            else if (casBase(v = base, ((fn == null) ? v + x :
                                        fn.applyAsLong(v, x))))
                break;                          // Fall back on using base
        }
    }
条件递增,逐步解析

在这里插入图片描述
在这里插入图片描述
大致流程:

  • 最初无竞争时只更新base;
  • 如果更新base失败后,首次新建一个Cell[]数组
  • 当多个线程竞争同一个Cell比较激烈时,可能就要对Cell[]扩容
longAccumulate()

longAccumulate入参说明
在这里插入图片描述
Striped64中一些变量或者方法的定义
在这里插入图片描述
步骤

  • 线程hash值: probe
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 总纲
    在这里插入图片描述
    在这里插入图片描述

  • 刚刚要初始化Cell[]数组(首次新建),未初始化过Cell[]数组,尝试占有锁并首次初始化cells数组
    在这里插入图片描述
    在这里插入图片描述

  • 兜底, 多个线程尝试CAS修改失败的线程会走到这个分支
    在这里插入图片描述
    该分支实现直接操作base基数,将值累加到base上,也即其它线程正在初始化,多个线程正在更新base的值。

  • Cell数组不再为空且可能存在Cell数组扩容

    • 多个线程同时命中一个cell的竞争

总体代码
在这里插入图片描述

  • 1
    在这里插入图片描述
    在这里插入图片描述
  • 2
    在这里插入图片描述
  • 3
    在这里插入图片描述
    在这里插入图片描述
  • 4

在这里插入图片描述

  • 5
    在这里插入图片描述
  • 6
    在这里插入图片描述
  • 上6步总结
    在这里插入图片描述
sum()

sum()会将所有Cell数组中的value和base累加作为返回值。
核心的思想就是将之前AtomicLong一个value的更新压力分散到多个value中去,从而降级更新热点。

  • 为啥在并发情况下sum的值不精确
    sum执行时,并没有限制对base和cells的更新。所以LongAdder不是强一致性的,它是最终一致性的。
    首先,最终返回的sum局部变量,初始被复制为base,而最终返回时,很可能base已经被更新了,而此时局部变量sum不会更新,造成不一致。
    其次,这里对cell的读取也无法保证是最后一次写入的值。所以,sum方法在没有并发的情况下,可以获得正确的结果
    在这里插入图片描述
使用总结
  • AtomicLong
    • 线程安全,可允许一些性能损耗,要求高精度时可使用
    • 保证精度,性能代价
    • AtomicLong是多个线程针对单个热点值value进行原子操作
  • LongAdder
    • 当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
    • 保证性能,精度代价
    • LongAdder是每个线程拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作

总结

AtomicLong
  • 原理:
    • CAS+自旋
    • incrementAndGet
  • 场景:
    • 低并发下的全局计算
    • AtomicLong能保证并发情况下计数的准确性,其内部通过CAS来解决并发安全性的问题。
  • 缺陷:
    • 高并发后性能急剧下降
    • AtomicLong的自旋会成为瓶颈
      N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。
LongAdder vs AtomicLong Performance

http://blog.palominolabs.com/2014/02/10/java-8-performance-improvements-longadder-vs-atomiclong/

LongAdder
  • 原理:
    • CAS+Base+Cell数组分散
    • 空间换时间并分散了热点数据
  • 场景:
    • 高并发下的全局计算
  • 缺陷:
    • sum求和后还有计算线程修改结果的话,最后结果不够准确
      N个线程CAS操作修改线程的值,每次只有一个成功过,其它N - 1失败,失败的不停的自旋直到成功,这样大量失败自旋的情况,一下子cpu就打高了。

猜你喜欢

转载自blog.csdn.net/qq_43478625/article/details/121550303