【JUC】原子操作类

【JUC】原子操作类

1. 原子操作类

原子操作类如下所示:

  1. AtomicBoolean
  2. AtomicInteger
  3. AtomicIntegerArray
  4. AtomicIntegerFieldUpdater
  5. AtomicLong
  6. AtomicLongArray
  7. AtomicLongFieldUpdater
  8. AtomicMarkableReference
  9. AtomicReference
  10. AtomicReferenceArray
  11. AtomicReferenceFieldUpdater
  12. AtomicStampedReference
  13. DoubleAccumulator
  14. DoubleAdder
  15. LongAccumulator
  16. LongAdder

根据每个类的特点可以将其分成几种不同的类型:

1.1 基本类型原子类

基本类型原子类包含以下三类:

  1. AtomicInteger
  2. AtomicBoolean
  3. AtomicLong

AtomicInteger 为例,其常用API如下(其他类也差不多):

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

AtomicInteger 演示 i++

class MyNumber {
    
    
    AtomicInteger atomicInteger = new AtomicInteger();

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

public class AtomicIntegerDemo {
    
    
    public static final int SIZE = 50;

    public static void main(String[] args) throws InterruptedException {
    
    
        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 {
    
    
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        
        //等待上面50个线程全部计算完成后,再去获得最终值
        countDownLatch.await();

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

运行结果如下:

image-20230422173820024


1.2 数组类型原子类

数组类型原子类包含以下三类:

  1. AtomicIntegerArray
  2. AtomicLongArray
  3. AtomicReferenceArray

AtomicIntegerArray 演示基本用法:

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

        int tmpInt = 0;

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

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

运行结果如下所示:

image-20230422174509323


1.3 引用类型原子类

引用类型原子类包括以下三类:

  1. AtomicReference
  2. AtomicStampedReference
    • 携带版本号的引用类型原子类,可以解决 ABA问题
    • 解决修改过几次
    • 状态戳原子引用
  3. AtomicMarkableReference
    • 原子更新带有标记位的引用类型对象
    • 解决是否修改过(将状态戳简化为true/false)
    • 状态戳(true/false)原子引用

1.3.1 AtomicReference

AtomicReference 使用实例:

class User {
    
    
    String userName;
    int age;
}

/**
 * @auther zzyy
 * @create 2022-02-24 14:50
 */
public class AtomicReferenceDemo {
    
    
    public static void main(String[] args) {
    
    
        AtomicReference<User> atomicReference = new AtomicReference<>();

        User z3 = new User("z3", 22);
        User li4 = new User("li4", 28);

        atomicReference.set(z3);

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


    }
}

运行结果如下:

image-20230422211147704

使用 AtomicReference 实现一个自旋锁:

需求:

  1. 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟
  2. B随后进来后发现,当前有线程持有锁,所以只能通过自旋等待,直到A释放锁后B随后抢到。
public class SpinLockDemo {
    
    
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

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

        }
    }

    public void unLock() {
    
    
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t" + "----task over,unLock...");
    }

    public static void main(String[] args) {
    
    
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
    
    
            spinLockDemo.lock();
            //暂停几秒钟线程
            try {
    
    
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            spinLockDemo.unLock();
        }, "A").start();

        //暂停500毫秒,线程A先于B启动
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(500);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }

        new Thread(() -> {
    
    
            spinLockDemo.lock();

            spinLockDemo.unLock();
        }, "B").start();


    }
}

1.3.2 AtomicStampedReference

携带版本号的引用类型原子类,可以解决 ABA问题 。可以记录修改过几次变量。

示例如下:

public class ABADemo {
    
    
    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() + "\t" + "首次版本号:" + stamp);

            //暂停500毫秒,保证后面的t4线程初始化拿到的版本号和我一样
            try {
    
    
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            stampedReference.compareAndSet(100, 101, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + "2次流水号:" + stampedReference.getStamp());

            stampedReference.compareAndSet(101, 100, stampedReference.getStamp(), stampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t" + "3次流水号:" + stampedReference.getStamp());

        }, "t3").start();

        new Thread(() -> {
    
    
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t" + "首次版本号:" + stamp);

            //暂停1秒钟线程,等待上面的t3线程,发生了ABA问题
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            boolean b = stampedReference.compareAndSet(100, 2022, stamp, stamp + 1);

            System.out.println(b + "\t" + stampedReference.getReference() + "\t" + stampedReference.getStamp());

        }, "t4").start();

    }
}

运行结果如下:

image-20230422211701736


1.3.3 AtomicMarkableReference

原子更新带有标记位的引用类型对象,它的定义就是将状态戳简化为 true/false

示例代码:

public class AtomicMarkableReferenceDemo {
    
    
    static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);
            //暂停1秒钟线程,等待后面的T2线程和我拿到一样的模式flag标识,都是false
            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            markableReference.compareAndSet(100, 1000, marked, !marked);
        }, "t1").start();

        new Thread(() -> {
    
    
            boolean marked = markableReference.isMarked();
            System.out.println(Thread.currentThread().getName() + "\t" + "默认标识:" + marked);

            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            boolean b = markableReference.compareAndSet(100, 2000, marked, !marked);
            System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASresult: " + b);
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());
            System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());
        }, "t2").start();
    }
}

运行结果:

image-20230422211918106


1.4 对象的属性修改原子类

对象的属性修改原子类包含以下三种:

  1. AtomicIntegerFieldUpdater:原子更新对象中int类型字段的值
  2. AtomicLongFieldUpdater:原子更新对象中Long类型字段的值
  3. AtomicReferenceFieldUpdater:原子更新引用类型字段的值

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

使用要求:

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

1.4.1 AtomicIntegerFieldUpdater

使用示例:

1)需求:

  • 以一种线程安全的方式操作非线程安全对象的某些字段。
  • 10个线程,每个线程转账1000,
  • 不使用synchronized,尝试使用AtomicIntegerFieldUpdater来实现。

代码如下:

class BankAccount//资源类
{
    
    
    String bankName = "CCB";

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

    public void add() {
    
    
        money++;
    }

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

    //不加synchronized,保证高性能原子性,局部微创小手术
    public void transMoney() {
    
    
        fieldUpdater.getAndIncrement(this);
    }


}

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

        for (int i = 1; i <= 10; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    for (int j = 1; j <= 1000; j++) {
    
    
                        //bankAccount.add();
                        bankAccount.transMoney();
                    }
                } finally {
    
    
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }

        countDownLatch.await();

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

运行结果如下:

image-20230424000529363


1.4.2 AtomicReferenceFieldUpdater

使用示例:

需求:

  1. 一个资源类,多个线程过来只有第一个能够对其进行初始化
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" + "----- start init,need 2 seconds");
            //暂停几秒钟线程
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "\t" + "----- over init");
        } else {
    
    
            System.out.println(Thread.currentThread().getName() + "\t" + "----- 已经有线程在进行初始化工作。。。。。");
        }
    }
}


/**
 * @auther zzyy
 * 需求:
 * 多线程并发调用一个类的初始化方法,如果未被初始化过,将执行初始化工作,
 * 要求只能被初始化一次,只有一个线程操作成功
 */
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();
        }
    }
}

运行结果如下:

image-20230428214802374


1.5 原子操作增强类!!!

  1. LongAdder
  2. LongAccumulator
  3. DoubleAdder
  4. DoubleAccumulator

1.5.1 LongAdder

LongAdder 只能用来计算加法,且从零开始计算

常用API:

image-20230428220559775

使用示例:

public class LongAdderAPIDemo {
    
    
    public static void main(String[] args) {
    
    
        LongAdder longAdder = new LongAdder();

        longAdder.increment();
        longAdder.increment();
        longAdder.increment();
        System.out.println(longAdder.sum());

        longAdder.add(-4);
        System.out.println(longAdder.sum());
        
        longAdder.reset();
        System.out.println(longAdder.sum());
    }
}

运行结果如下:

image-20230428220907569


1.5.2 LongAccumulator

LongAccumulator 提供了自定义的函数操作。long类型的聚合器,需要传入一个long类型的二元操作,可以用来计算各种聚合操作,包括加乘等。

使用示例:

public class LongAdderAPIDemo {
    
    
    public static void main(String[] args) {
    
    
        //LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator()
        //{
    
    
        //    @Override
        //    public long applyAsLong(long left, long right)
        //    {
    
    
        //        return left * right;
        //    }
        //},0);

        //使用lambda表达式更加简洁,可以自定义加减乘除操作,以及初始值
        LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x / y, 5);
        System.out.println(longAccumulator.get());

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

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

运行结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bd4oFNrz-1682785708670)(http://blog.tempeisite.xyz/blog/image-20230428221452465.png)]


1.5.3 性能分析!!!

场景:热点商品点赞计算器,点赞数加加统计,不要求实时精确

需求:50个线程,每个线程100w次,计算总点赞数

class ClickNumber //资源类
{
    
    
    int number = 0;

    //方式1
    public synchronized void clickBySynchronized() {
    
    
        number++;
    }

    AtomicLong atomicLong = new AtomicLong(0);

    //方式2
    public void clickByAtomicLong() {
    
    
        atomicLong.getAndIncrement();
    }

    LongAdder longAdder = new LongAdder();

    //方式3
    public void clickByLongAdder() {
    
    
        longAdder.increment();
    }

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

    //方式4
    public void clickByLongAccumulator() {
    
    
        longAccumulator.accumulate(1);
    }

}

/**
 * 需求: 50个线程,每个线程100W次,总点赞数出来
 */
public class AccumulatorCompareDemo {
    
    
    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 startTime;
        long endTime;

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

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

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


        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    for (int j = 1; j <= 100 * _1W; j++) {
    
    
                        clickNumber.clickByLongAdder();
                    }
                } finally {
    
    
                    countDownLatch3.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch3.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());

        startTime = System.currentTimeMillis();
        for (int i = 1; i <= threadNumber; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    for (int j = 1; j <= 100 * _1W; j++) {
    
    
                        clickNumber.clickByLongAccumulator();
                    }
                } finally {
    
    
                    countDownLatch4.countDown();
                }
            }, String.valueOf(i)).start();
        }
        countDownLatch4.await();
        endTime = System.currentTimeMillis();
        System.out.println("----costTime: " + (endTime - startTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());

    }
}

运行结果:

image-20230428225624816

由此可见:原子操作增强类的性能远比其他类更加高效。


1.5.3.1 LongAdder 为什么这么快

LongAdder 比 AtomicLong 性能更好,因为减少了乐观锁的重试次数。

架构:

image-20230428225816227

LongAdder 是 Striped64 的子类

image-20230428225847621

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;

其中最重要的是如下这两个:

image-20230428230253054

Celljava.util.concurrent.atomicStriped64 的一个内部类

image-20230428230736830

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

image-20230428230401877

1.5.3.1.1 原因

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

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

base变量:在非竟态条件下,直接累加到该变量上。

Cell[]数组:竟态条件下,累加到各个线程自己的槽 Cell[i] 中

image-20230428234259166

image-20230428234321691


1.4.3.1.2 总结及源码分析

总结:

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

image-20230429000812468

LongAdder 的 add 方法解释:

image-20230429225148852

image-20230429225238132

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

LongAdder 中 longAccumulate 方法:

image-20230430000543385

image-20230430000606020


1.5.4 总结对比

1.5.4.1 AtomicLong

特点:

  • 线程安全,可允许一些性能损耗,要求高精度时可使用
  • 保证精度,性能代价
  • 多个线程针对单个热点值value进行原子操作

原理:

  • CAS + 自旋
  • incrementAndGet

场景:

  • 低并发下的全局计算
  • 能保证并发情况下计算的准确性,其内部通过 CAS 来解决并发安全性问题

缺陷:

  • 高并发后性能急剧下降(N个线程CAS操作修改线程的值,每次只有一个成功,其他N-1个线程失败,失败的线程会不停的自旋直到成功,这样大量失败自旋的情况,会让cpu飙高)

1.5.4.2 LongAdder

特点:

  • 当需要在高并发下有较好的性能表现,且对值的精确度要求不高时,可以使用
  • 保证性能,精度代价
  • LongAdder是每个线程都拥有自己的槽,各个线程一般只对自己槽中的那个值进行CAS操作

原理:

  • CAS + Base + Cell数组分散
  • 空间换时间并分散了热点数据

场景:

  • 高并发下的全局计算

缺陷:

  • sum求和后还有计算线程修改结果的话,最后结果不够准确

猜你喜欢

转载自blog.csdn.net/Decade_Faiz/article/details/130310748