1. 原子类是什么

原子类包装了一个变量,然后提供对这个变量的原子操作的方法。

注意:原子类中对变量的操作,都是原子操作。

2. 原子类有什么用

把变量的操作封装成原子操作,也就是保证了原子性。

多线程的三大特性:原子性、有序性、可见性

换句话说,当你的代码保证了有序性和可见性时,可以使用原子类来保证原子性,从而避免synchronized带来的高性能开销。

3. 有哪些原子类

JDK在1.5时提供了很多原子类,在java.util.concurrent.atomic包下

Java的类原子加加 java中的原子类_原子类

分为四类

  1. 基本原子类
  1. 布尔型:AtomicBoolean
  2. 整型:AtomicInteger
  3. 长整形:AtomicLong

布尔型与整形类似,通过操作0和1实现

  1. 数组原子类
  1. 整型数组:AtomicIntegerArray
  2. 长整型数组:AtomicLongArray
  3. 引用型数组:AtomicReferenceArray
  1. 引用原子类
  1. AtomicReference:原子更新引用类型。
  2. AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
  3. AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)
  1. 字段原子类
  1. AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  2. AtomicLongFieldUpdater:原子更新长整型字段的更新器。
  3. AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于原子的更数据和数据的版本号,可以解决使用CAS进行原子更新时,可能出现的ABA问题。

4. 怎么去使用

以AtomicInteger为例

用i++的例子介绍整型原子类的使用

public class AtomicTest {

    private static volatile int i;

    private static void increase() {
        i++;
    }

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[20];
        for (int k = 0; k < 20; k++) {
            threads[k] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    increase();
                }
            });
            threads[k].start();
        }

        for (Thread thread : threads) {
            while (thread.isAlive()) {
                Thread.sleep(100);
            }
        }
        System.out.println(i);
    }
}

输出

74858

这个例子很简单,起20个线程,每个线程执行1万次i++操作,预期结果应该是20万,实际结果却是7万多。

原因是,虽然volatile保证了有序性和可见性,但是不能保证原子性,i++不是原子操作。

所以在这里,我们只需要保证i++操作是原子操作,就不用使用笨重的synchronized了

怎么保证呢?使用JDK提供的原子类AtomicInteger

public class AtomicTest {

//    private static volatile int i;
    private static AtomicInteger atomicInteger = new AtomicInteger();

    private static void increase() {
//        i++;
        atomicInteger.getAndIncrement();
    }

    public static void main(String[] args) throws Exception {
        Thread[] threads = new Thread[20];
        for (int k = 0; k < 20; k++) {
            threads[k] = new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    increase();
                }
            });
            threads[k].start();
        }

        for (Thread thread : threads) {
            while (thread.isAlive()) {
                Thread.sleep(100);
            }
        }
//        System.out.println(i);
        System.out.println(atomicInteger.get());
    }
}

输出

200000

预期结果与实际结果一致

5. 实现原理-CAS

CAS(Compare and swap)

底层调用native方法,也就是JVM通过CPU的指令实现

当前的处理器基本都支持CAS,只不过每个厂家所实现的算法并不一样罢了。

原理:

  1. 需要读写的内存址(V)、原值(A)和新值(B)。如果V的值与原值A相匹配,那么把B设置给V,否则处理器不做任何操作。
  2. 无论哪种情况,都返回V当前值。
  3. 原子类里,当失败时,就一直循环,直到成功。

12是在CPU和内存的层面来说的,3是在Java层面说的

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}

6. 注意

  1. 原子类并不是提供真正的原子操作,其方法在执行时,还是可能被其它线程打断的,只是它在被打断后(共享变量改变),会获取新的值重新操作,一直重复到成功。所以在外部看来,它就是一个原子操作。
  2. CAS是非阻塞的。
  3. CAS的ABA问题:当然CAS也并不完美,它存在"ABA"问题,假若一个变量初次读取是A,在compare阶段依然是A,但其实可能在此过程中,它先被改为B,再被改回A,而CAS是无法意识到这个问题的。CAS只关注了比较前后的值是否改变,而无法清楚在此过程中变量的变更明细,这就是所谓的ABA漏洞。