• JUC——原子类


    1.基本类型原子操作类

    • AtomicInteger:整型原子类

    • AtomicBoolean:布尔型原子类

    • AtomicLong:长整型原子类

    1.1常用API

    1. public final int get() //获取当前的值
    2. public final int getAndSet(int newValue)//获取当前的值,并设置新的值
    3. public final int getAndIncrement()//获取当前的值,并自增
    4. public final int getAndDecrement() //获取当前的值,并自减
    5. public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
    6. boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
    7. public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

    2.数组类型原子操作类

    • AtomicIntegerArray:整型数组原子类

    • AtomicLongArray:长整型数组原子类

    • AtomicRefereceArray:引用类型数组原子类

    2.1常用API

    1. public final int get(int i) //获取 index=i 位置元素的值
    2. public final int getAndSet(int i,int newValue)//返回index=i位置的当前的值,并将其设置为新值:newValue
    3. public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
    4. public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
    5. public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
    6. boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
    7. public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

    3.引用类型原子操作类

    • AtomicReference:引用类型原子类

    • AtomicStampedReference:原子更新带有版本号,可解决CAS中的ABA问题,记录的是修改过几次。

    • AtomicMarkableReference:原子更新带有标记(false/true),记录的是是否被修改过。

    3.1AtomicMarkableReference的使用

    1. public class Test4 {
    2. static AtomicMarkableReference markableReference = new AtomicMarkableReference(100, false);
    3. public static void main(String[] args) {
    4. new Thread(() -> {
    5. boolean marked = markableReference.isMarked();
    6. System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t1 默认标识: false
    7. try {
    8. TimeUnit.SECONDS.sleep(1);
    9. } catch (InterruptedException e) {
    10. e.printStackTrace();
    11. }
    12. markableReference.compareAndSet(100, 1000, marked, !marked); //t1进行修改 mark->true
    13. }, "t1").start();
    14. new Thread(() -> {
    15. boolean marked = markableReference.isMarked();
    16. System.out.println(Thread.currentThread().getName() + "\t" + "默认标识: " + marked);//t2 默认标识: false
    17. try {
    18. TimeUnit.SECONDS.sleep(2);
    19. } catch (InterruptedException e) {
    20. e.printStackTrace();
    21. }
    22. boolean b = markableReference.compareAndSet(100, 2000, marked, !marked); //因为t1已经改过了,返回false
    23. System.out.println(Thread.currentThread().getName() + "\t" + "t2线程CASResult:" + b); //t2 t2线程CASResult:false
    24. System.out.println(Thread.currentThread().getName() + "\t" + markableReference.isMarked());//t2 true
    25. System.out.println(Thread.currentThread().getName() + "\t" + markableReference.getReference());//t2 1000
    26. }, "t2").start();
    27. }
    28. }

    4.对象的属性修改原子操作类

    • AtomicIntegerFieldUpdater:原子更新对象中int类型的属性。

    • AtomicLongFieldUpdater:原子更新对象中的Long类型的属性。

    • AtomicReferenceFieldUpdater:原子更行对象中引用类型的属性。

    4.1使用原因

    因为Synchronized锁的是整个对象,为了减少锁的粒度,我们使用对象属性原子操作类。

    4.2使用要求

    1. 属性必须使用public volatile修饰。

    2. 需要使用newUpdater()创建更新器。

    4.3AtomicReferenceFieldUpdater的使用

    1. //只能有一个线程对其初始化
    2. class MyVar {
    3. public volatile Boolean isInit = Boolean.FALSE;
    4. AtomicReferenceFieldUpdater referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(MyVar.class, Boolean.class, "isInit");
    5. public void init(MyVar myVar) {
    6. //比较并交换进行修改
    7. if (referenceFieldUpdater.compareAndSet(myVar, Boolean.FALSE, Boolean.TRUE)) {
    8. System.out.println(Thread.currentThread().getName() + "\t" + "--------------start init ,need 2 secondes");
    9. try {
    10. TimeUnit.SECONDS.sleep(2);
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. System.out.println(Thread.currentThread().getName() + "\t" + "--------------over init");
    15. } else {
    16. System.out.println(Thread.currentThread().getName() + "\t" + "--------------已经有线程进行初始化工作了。。。。。");
    17. }
    18. }
    19. }
    20. public class AtomicReferenceFieldUpdaterDemo {
    21. public static void main(String[] args) {
    22. MyVar myVar = new MyVar();
    23. for (int i = 1; i <= 5; i++) {
    24. new Thread(() -> {
    25. myVar.init(myVar);
    26. }, String.valueOf(i)).start();
    27. }
    28. }
    29. }
    30. /**
    31. * 1 --------------start init ,need 2 secondes
    32. * 5 --------------已经有线程进行初始化工作了。。。。。
    33. * 2 --------------已经有线程进行初始化工作了。。。。。
    34. * 4 --------------已经有线程进行初始化工作了。。。。。
    35. * 3 --------------已经有线程进行初始化工作了。。。。。
    36. * 1 --------------over init
    37. */

     

    5.原子操作增强类

    • LongAdder:维护了一个初始值从0开始的long类型的数据,只能用来计算加法,相较于AtomciLong,减少了自旋的次数。

    • LongAccumulator:相较于LongAdder,提供了自定义的函数操作,更加的灵活便捷。

    5.1实现计数器的各种方法比较

    1. /**
    2. * 需求:50个线程,每个线程100w此,算出总点赞数来
    3. */
    4. class ClickNumber {
    5. int number = 0;
    6. public synchronized void clickBySynchronized() {
    7. number++;
    8. }
    9. AtomicLong atomicLong = new AtomicLong(0);
    10. public void clickByAtomicLong() {
    11. atomicLong.getAndIncrement();
    12. }
    13. LongAdder longAdder = new LongAdder();
    14. public void clickByLongAdder() {
    15. longAdder.increment();
    16. }
    17. LongAccumulator longAccumulator = new LongAccumulator((x, y) -> x + y, 0);
    18. public void clickByLongAccumulator() {
    19. longAccumulator.accumulate(1);
    20. }
    21. }
    22. public class AccumulatorCompareDemo {
    23. public static final int _1W = 10000;
    24. public static final int THREAD_NUMBER = 50;
    25. public static void main(String[] args) throws InterruptedException {
    26. ClickNumber clickNumber = new ClickNumber();
    27. long StartTime;
    28. long endTime;
    29. CountDownLatch countDownLatch1 = new CountDownLatch(THREAD_NUMBER);
    30. CountDownLatch countDownLatch2 = new CountDownLatch(THREAD_NUMBER);
    31. CountDownLatch countDownLatch3 = new CountDownLatch(THREAD_NUMBER);
    32. CountDownLatch countDownLatch4 = new CountDownLatch(THREAD_NUMBER);
    33. StartTime = System.currentTimeMillis();
    34. for (int i = 1; i <= 50; i++) {
    35. new Thread(() -> {
    36. try {
    37. for (int j = 1; j <= 100 * _1W; j++) {
    38. clickNumber.clickBySynchronized();
    39. }
    40. } finally {
    41. countDownLatch1.countDown();
    42. }
    43. }, String.valueOf(i)).start();
    44. }
    45. countDownLatch1.await();
    46. endTime = System.currentTimeMillis();
    47. System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickBySynchronized: " + clickNumber.number);
    48. StartTime = System.currentTimeMillis();
    49. for (int i = 1; i <= 50; i++) {
    50. new Thread(() -> {
    51. try {
    52. for (int j = 1; j <= 100 * _1W; j++) {
    53. clickNumber.clickByAtomicLong();
    54. }
    55. } finally {
    56. countDownLatch2.countDown();
    57. }
    58. }, String.valueOf(i)).start();
    59. }
    60. countDownLatch2.await();
    61. endTime = System.currentTimeMillis();
    62. System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByAtomicLong: " + clickNumber.atomicLong.get());
    63. StartTime = System.currentTimeMillis();
    64. for (int i = 1; i <= 50; i++) {
    65. new Thread(() -> {
    66. try {
    67. for (int j = 1; j <= 100 * _1W; j++) {
    68. clickNumber.clickByLongAdder();
    69. }
    70. } finally {
    71. countDownLatch3.countDown();
    72. }
    73. }, String.valueOf(i)).start();
    74. }
    75. countDownLatch3.await();
    76. endTime = System.currentTimeMillis();
    77. System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAdder: " + clickNumber.longAdder.sum());
    78. StartTime = System.currentTimeMillis();
    79. for (int i = 1; i <= 50; i++) {
    80. new Thread(() -> {
    81. try {
    82. for (int j = 1; j <= 100 * _1W; j++) {
    83. clickNumber.clickByLongAccumulator();
    84. }
    85. } finally {
    86. countDownLatch4.countDown();
    87. }
    88. }, String.valueOf(i)).start();
    89. }
    90. countDownLatch4.await();
    91. endTime = System.currentTimeMillis();
    92. System.out.println("------costTime: " + (endTime - StartTime) + " 毫秒" + "\t clickByLongAccumulator: " + clickNumber.longAccumulator.get());
    93. }
    94. }
    95. /**
    96. * ------costTime: 1313 毫秒 clickBySynchronized: 50000000
    97. * ------costTime: 825 毫秒 clickByAtomicLong: 50000000
    98. * ------costTime: 92 毫秒 clickByLongAdder: 50000000
    99. * ------costTime: 61 毫秒 clickByLongAccumulator: 50000000
    100. */

    5.2LongAdder为什么快?(源码分析)

            先从架构上进行分析,LongAdder继承了Striped64,Striped64继承了Number类,主要是Striped64类起到了关键性的作用。

    具体为什么快?

            AtomicLong采用的是利用CAS原则对value值进行原子操作,这样多线程环境下的自旋会导致cpu资源大量的消耗,LongAdder的改进思路就是分散热点,在并发量小的情况下,仅对base进行原子操作,在并发量大的情况下,采用了Cell槽位,槽位里面用value值进行记录,这样不同的线程会命中到不同的槽位中,各个线程只对自己槽中的那个值进行CAS操作,这样的话高并发就被分散开了,要想获取到总的值,只需统计各个槽位中的value值和base的值,但是统计的数据不会高度一致(因为当sum统计时,还能对槽位数据进行操作),只能保证最终一致。

     

    1. 如果Cells数组为空,尝试用CAS更新base,成功则退出即可。

    2. 如果Cells数组为空,但是高并发下CAS更新base失败了,则调用longAccumulate()方法创建槽位,初始创建2个槽位,再次尝试CAS更新base,如果还是不行,则走“槽位法”。

    3. 如果Cells数组非空,但是当前的线程利用getProbe()算出的hash值映射到的槽位为空,则调用longAccumlate()方法对指定位置创建槽位并添加到Cell数组中,然后重新计算hash。

    4. 如果Cells数组非空,且当前线程映射的槽位不为空,CAS更新映射到的槽位的value值,成功则返回,否则,说明槽位竞争都非常大了,调用longAccumlate()方法对槽位进行扩容。

    注意:槽位的个数只能为2^n个,最大不能超过CPU的核数。

    5.3AtomicLong和LongAdder小总结

            AtomicLong可允许一些性能损耗,要求高精度时可使用,保证精度,多个线程对单个热点值value进行了原子操作,从而保证精度,但对Cpu资源的损耗较大。

            LongAdder当需要在高并发场景下有较好的性能表现,且对值的实时精确度要求不高时,可以使用,LongAdder中线程拥有自己的槽,各个线程只对自己槽中得那个value值进行CAS操作,从而保证性能,但牺牲了实时的精度代价,不过一定是能保证最终一致性的。

     

  • 相关阅读:
    升级iOS 17出现白苹果、不断重启等系统问题怎么办?
    5-1.Binding的方向、数据更新和Path
    Dart 异常详解
    分析思路:数据结构
    Swift 周报 第十六期
    JVM的运行原理
    AI应用新时代的起点,亚马逊云科技加速大模型应用
    【正点原子STM32连载】 第二十五章 TFTLCD(MCU屏)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1
    安装黑群晖不求人,arpl在线编译安装群晖教程
    纯JSP MySQL分页
  • 原文地址:https://blog.csdn.net/m0_62565675/article/details/133544321