什么是原子性?
如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行。这种特性就叫原子性
原子问题演示
在java中i++这种操作不具有原子性,以此作为演示,在多线程情况下对++操作的影响。
i++操作实现过程:
- a = i
- a = a+1
- i=a
代码如下:
package threadcontrol;
public class ThreadAtomicDemo {
private static int num = 0;
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(()->{
for (int i = 0; i< 1000; i++) {
num++;
}
},"Thread1");
Thread thread2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
num++;
}
},"Thread2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("最终结果:"+num);
}
}
预期结果为2000,而实际结果呢?
可以看到实际结果是1621(如果反复多次运行还会得到其他不同的结果),这是由于++操作不具有原子性造成的,要解决这一问题可以使用Java中的原子类。
原子类
Java 的原子类都存放在并发包 java.util.concurrent.atomic
下,如下图所示。
主要包括如下几类:
基本类型
使用原子的方式更新基本类型
- AtomicInteger:整形原子类
- AtomicLong:长整型原子类
- AtomicBoolean:布尔型原子类
数组类型
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray:整形数组原子类
- AtomicLongArray:长整形数组原子类
- AtomicReferenceArray:引用类型数组原子类
引用类型
- AtomicReference:引用类型原子类
- AtomicStampedReference:原子更新引用类型里的字段原子类
- AtomicMarkableReference :原子更新带有标记位的引用类型
对象的属性修改类型
- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
- AtomicLongFieldUpdater:原子更新长整形字段的更新器
- AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,以及解决使用 CAS 进行原子更新时可能出现的 ABA 问题
修改后的代码:
package threadcontrol;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadAtomicDemo {
//private static int num = 0;
private static AtomicInteger atomicInteger;
public static void main(String[] args) throws InterruptedException {
atomicInteger = new AtomicInteger();
Thread thread1 = new Thread(()->{
for (int i = 0; i< 1000; i++) {
// num++;
atomicInteger.getAndIncrement();
}
},"Thread1");
Thread thread2 = new Thread(()->{
for (int i = 0; i < 1000; i++) {
// num++;
atomicInteger.getAndIncrement();
}
},"Thread2");
thread1.start();
thread2.start();
thread1.join();
thread2.join();
// System.out.println("最终结果:"+num);
System.out.println("最终结果:"+atomicInteger.get());
}
}
运行结果:
CAS
CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”
通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。
类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败)算法 可以对该操作重新计算。
参考
https://www.jianshu.com/p/c5e4e7921adc