1. 原子类是什么
原子类包装了一个变量,然后提供对这个变量的原子操作的方法。
注意:原子类中对变量的操作,都是原子操作。
2. 原子类有什么用
把变量的操作封装成原子操作,也就是保证了原子性。
多线程的三大特性:原子性、有序性、可见性,详情可了解(Java内存模型-工作内存、主内存、原子性、有序性、可见性、volatile、synchronized)
换句话说,当你的代码保证了有序性和可见性时,可以使用原子类来保证原子性,从而避免synchronized带来的高性能开销。
3. 有哪些原子类
JDK在1.5时提供了很多原子类,在java.util.concurrent.atomic
包下
分为四类
- 基本原子类
- 布尔型:AtomicBoolean
- 整型:AtomicInteger
- 长整形:AtomicLong
布尔型与整形类似,通过操作0和1实现
- 数组原子类
- 整型数组:AtomicIntegerArray
- 长整型数组:AtomicLongArray
- 引用型数组:AtomicReferenceArray
- 引用原子类
- AtomicReference:原子更新引用类型。
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段。
- AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子的更新一个布尔类型的标记位和引用类型。构造方法是AtomicMarkableReference(V initialRef, boolean initialMark)
- 字段原子类
- AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
- AtomicLongFieldUpdater:原子更新长整型字段的更新器。
- 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,只不过每个厂家所实现的算法并不一样罢了。
原理:
- 需要读写的内存址(V)、原值(A)和新值(B)。如果V的值与原值A相匹配,那么把B设置给V,否则处理器不做任何操作。
- 无论哪种情况,都返回V当前值。
- 原子类里,当失败时,就一直循环,直到成功。
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. 注意
- 原子类并不是提供真正的原子操作,其方法在执行时,还是可能被其它线程打断的,只是它在被打断后(共享变量改变),会获取新的值重新操作,一直重复到成功。所以在外部看来,它就是一个原子操作。
- CAS是非阻塞的。
- CAS的ABA问题:当然CAS也并不完美,它存在"ABA"问题,假若一个变量初次读取是A,在compare阶段依然是A,但其实可能在此过程中,它先被改为B,再被改回A,而CAS是无法意识到这个问题的。CAS只关注了比较前后的值是否改变,而无法清楚在此过程中变量的变更明细,这就是所谓的ABA漏洞。