atomic原子类

atomic包中是一些基于CAS实现的工具类,前面已经介绍过CAS并且演示了AtomicStampedReference的使用,这一部分主要演示一下其他工具类的使用,就不过多的进行分析。

AtomicBoolean

AtomicBoolean提供了原子的更新某些标志位的功能,由JDK1.5引入,并不能用作boolean的替代品。AtomicBoolean的适用场景不多,适合一些并发初始化且只需要初始化一次资源的场景,也可以用它来优雅的初始化资源。

  1. package atomic;
  2. import java.util.concurrent.atomic.AtomicBoolean;
  3. /**
  4. * BooleanTest
  5. *
  6. * @author starsray
  7. * @date 2021/12/21
  8. */
  9. public class BooleanTest {
  10. /**
  11. * 初始化
  12. */
  13. private static AtomicBoolean initialized = new AtomicBoolean(false);
  14. /**
  15. * 初始化
  16. */
  17. public void init() {
  18. if (initialized.compareAndSet(false, true)) {
  19. //here is the initialization code
  20. }
  21. }
  22. }

AtomicInteger

AtomicInteger是原子的对int类型的数字进行加减操作,由JDK1.5引入,相对于添加synchronized块的计数功能,基于CAS实现的AtomicInteger要更加高效率一点。通过一个示例来演示AtomicInteger的使用,开启2个线程测试从0增加到1000_000消耗的时间,并打印预期结果:

  • testSync使用synchronized
  • testAtomic使用AtomicInteger ```java package atomic;

import java.time.Duration; import java.time.Instant; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger;

/**

  • 整数测试 *
  • @author starsray
  • @date 2021/12/21 / public class IntegerTest { /*

    • 阈值 */ private static final int threshold = 1000_000;

      /**

    • 初始化 / private static int init = 0; /*
    • 最初的 */ private static final AtomicInteger initial = new AtomicInteger(0);

      /**

    • 线程数 / private static final int threadCount = 2; /*
    • 锁 */ static final Object lock = new Object();

      /**

    • 主要 *
    • @param args arg游戏
    • @throws InterruptedException 中断异常 */ public static void main(String[] args) throws InterruptedException { testSync(); testAtomic(); }

      /**

    • 测试同步 *
    • @throws InterruptedException 中断异常 */ static void testSync() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Instant start = Instant.now(); for (int i = 0; i < threadCount; i++) {

      1. new Thread(() -> {
      2. while (true) {
      3. synchronized (lock) {
      4. if (init < threshold) {
      5. init++;
      6. } else {
      7. countDownLatch.countDown();
      8. break;
      9. }
      10. }
      11. }
      12. }).start();

      } countDownLatch.await(); Instant end = Instant.now(); System.out.printf(“sync result:%s%n”, init); System.out.printf(“sync result cost:%s%n”, Duration.between(start, end).toMillis()); }

      /**

    • 测试原子 *
    • @throws InterruptedException 中断异常 */ static void testAtomic() throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(1); Instant start = Instant.now(); for (int i = 0; i < threadCount; i++) {
      1. new Thread(() -> {
      2. while (true) {
      3. if (initial.intValue() < threshold){
      4. initial.incrementAndGet();
      5. }else{
      6. countDownLatch.countDown();
      7. break;
      8. }
      9. }
      10. }).start();
      } countDownLatch.await(); Instant end = Instant.now(); System.out.printf(“atomic result:%s%n”, initial.intValue()); System.out.printf(“atomic result cost:%s%n”, Duration.between(start, end).toMillis()); } }
      1. 输出结果:
      2. ```java
      3. sync result:1000000
      4. sync result cost:151
      5. atomic result:1000001
      6. atomic result cost:13
      查看最终的执行时间,AtomicInteger不仅算的快,而且还多操作了一次递增,使用synchronized操作的结果是符合预期的,虽然慢了点,但是准确性更重要。AtomicInteger说好的线程安全怎么却不安全了呢?

其实并不是AtomicInteger不安全了,而是在使用的时候需要注意的一个点,在Java中i++并不是一个原子的操作,使用synchronized保证了线程独占,CPU在某个时间片只有一个线程可以对变量进行操作,即便不是原子操作也不会影响到计算结果,但是AtomicInteger只能保证把++包装成一个原子操作,并不能保证线程的独占,而这个实验中的计算结果1000001也说明了在最后一次递增前也就是threshold-1时,多个线程同时进入,在进入下一次循环时break才跳出循环。

出现问题的代码就在这一段:

  1. if (initial.intValue() < threshold){
  2. initial.incrementAndGet();
  3. }else{
  4. countDownLatch.countDown();
  5. break;
  6. }

讲代码进行调整,重新验证

  1. synchronized (lock){
  2. if (initial.intValue() < threshold){
  3. initial.incrementAndGet();
  4. }else{
  5. countDownLatch.countDown();
  6. break;
  7. }
  8. }

输出结果:

  1. sync result:1000000
  2. sync result cost:196
  3. atomic result:1000000
  4. atomic result cost:144

由于处理逻辑都是用到了synchronized所以效率差不多,按理说i++应该比incrementAndGet更快,这里也反映了AtomicInteger的效率是很高的。

上面的例子说明了在使用AtomicInteger时候要考虑场景,关于计数常见有下面的场景示例:

场景一 :——————————————————————————>| 已知起点、终点 场景二 :|——————————————————————————> 已知起点 场景三: |<—————————————————————————-| 已知终点 —> 0

场景三算是场景一的逆向特例,对于场景二更适合使用AtomicInteger原子类,因为第一种场景使用不能保证临界点线程独占,因此可能会有多个线程进入操作,从而导致结果的不准确。

  1. package atomic;
  2. import java.time.Duration;
  3. import java.time.Instant;
  4. import java.util.concurrent.CountDownLatch;
  5. import java.util.concurrent.atomic.AtomicInteger;
  6. /**
  7. * 智力测试
  8. *
  9. * @author starsray
  10. * @date 2021/12/22
  11. */
  12. public class IntTest {
  13. /**
  14. * 初始化
  15. */
  16. private static int init = 0;
  17. /**
  18. * 最初的
  19. */
  20. private static final AtomicInteger initial = new AtomicInteger(0);
  21. /**
  22. * 线程数
  23. */
  24. private static final int threadCount = 100;
  25. /**
  26. * 阈值
  27. */
  28. private static final int threshold = 1000_000;
  29. /**
  30. * 锁
  31. */
  32. private static final Object lock = new Object();
  33. /**
  34. * 主要
  35. *
  36. * @param args arg游戏
  37. * @throws InterruptedException 中断异常
  38. */
  39. public static void main(String[] args) throws InterruptedException {
  40. testSync();
  41. testAtomic();
  42. }
  43. /**
  44. * 测试同步
  45. *
  46. * @throws InterruptedException 中断异常
  47. */
  48. static void testSync() throws InterruptedException {
  49. Instant start = Instant.now();
  50. CountDownLatch countDownLatch = new CountDownLatch(threadCount);
  51. for (int i = 0; i < threadCount; i++) {
  52. new Thread(() -> {
  53. int j = 0;
  54. while (j < threshold) {
  55. j++;
  56. synchronized (lock) {
  57. init++;
  58. }
  59. }
  60. countDownLatch.countDown();
  61. }).start();
  62. }
  63. countDownLatch.await();
  64. Instant end = Instant.now();
  65. System.out.printf("sync result:%s%n", init);
  66. System.out.printf("sync result cost:%s%n", Duration.between(start, end).toMillis());
  67. }
  68. /**
  69. * 测试原子
  70. *
  71. * @throws InterruptedException 中断异常
  72. */
  73. static void testAtomic() throws InterruptedException {
  74. Instant start = Instant.now();
  75. CountDownLatch countDownLatch = new CountDownLatch(threadCount);
  76. for (int i = 0; i < threadCount; i++) {
  77. new Thread(() -> {
  78. int j = 0;
  79. while (j < threshold) {
  80. j++;
  81. initial.incrementAndGet();
  82. }
  83. countDownLatch.countDown();
  84. }).start();
  85. }
  86. countDownLatch.await();
  87. Instant end = Instant.now();
  88. System.out.printf("atomic result:%s%n", init);
  89. System.out.printf("atomic result cost:%s%n", Duration.between(start, end).toMillis());
  90. }
  91. }

输出结果:

  1. sync result:100000000
  2. sync result cost:6342
  3. atomic result:100000000
  4. atomic result cost:1868

明显的展示了基于CAS的AtomicInteger的速度,也保证了准确性。

AtomicLong/LongAccumulator/LongAdder

  • AtomicLong

由JDK1.5引入,使用方法同AtomicInteger,区别类似于Long与Integer,在表示数据长度上的区别。

  • LongAccumulator

JDK1.8引入,该类继承自Striped64类,查看该类都继承关系,及内部的属性方法:
LongAccumulator.png

  • Striped64类是JDK1.8引入。
  • Cell类是Striped64的内部类。

并发效率高的特性。

  • LongAdder

LongAdder是LongAccumulator的一个特例,LongAdder 类为维护计数和总和的常见特殊情况提供了此类功能的类似物。调用 new LongAdder() 等价于 如下代码:

  1. // LongAdder使用
  2. LongAdder longAdder = new LongAdder();
  3. // LongAccumulator使用
  4. LongAccumulator longAccumulator = new LongAccumulator(new LongBinaryOperator() {
  5. @Override
  6. public long applyAsLong(long left, long right) {
  7. return left + right;
  8. }
  9. }, 0);

使用Java新特性Lambda表达式及对象引用可以简写为:

  1. // LongAdder使用
  2. LongAdder longAdder = new LongAdder();
  3. // LongAccumulator使用
  4. LongAccumulator longAccumulator = new LongAccumulator(Long::sum, 0);

对于简单的递增递减可以使用LongAdder来进行操作,如果对于累加初始值非0,或者相对复杂的累加运算规则使用LongAccumulator。