「Java」
Automic 原子类
1 Atomic 原子类介绍
Atomic 是指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。
所以,所谓原子类说简单点就是具有原子/原子操作特征的类。
并发包 java.util.concurrent
的原子类都存放在java.util.concurrent.atomic
下,如下图所示。
根据操作的数据类型,可以将JUC包中的原子类分为4类
基本类型
使用原子的方式更新基本类型
- AtomicInteger:整型原子类
- AtomicLong:长整型原子类
- AtomicBoolean :布尔型原子类
数组类型
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray:整型数组原子类
- AtomicLongArray:长整型数组原子类
- AtomicReferenceArray :引用类型数组原子类
引用类型
- AtomicReference:引用类型原子类
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段
- AtomicMarkableReference :原子更新带有标记位的引用类型
对象的属性修改类型
- AtomicIntegerFieldUpdater:原子更新整型字段的更新器
- AtomicLongFieldUpdater:原子更新长整型字段的更新器
- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
- AtomicMarkableReference:原子更新带有标记的引用类型。该类将 boolean 标记与引用关联起来,也可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
CAS ABA 问题
- 描述: 第一个线程取到了变量 x 的值 A,然后巴拉巴拉干别的事,总之就是只拿到了变量 x 的值 A。这段时间内第二个线程也取到了变量 x 的值 A,然后把变量 x 的值改为 B,然后巴拉巴拉干别的事,最后又把变量 x 的值变为 A (相当于还原了)。在这之后第一个线程终于进行了变量 x 的操作,但是此时变量 x 的值还是 A,所以 compareAndSet 操作是成功。
- 例子描述(可能不太合适,但好理解): 年初,现金为零,然后通过正常劳动赚了三百万,之后正常消费了(比如买房子)三百万。年末,虽然现金零收入(可能变成其他形式了),但是赚了钱是事实,还是得交税的!
- 代码例子(以
AtomicInteger
为例)
1import java.util.concurrent.atomic.AtomicInteger;
2
3public class AtomicIntegerDefectDemo {
4 public static void main(String[] args) {
5 defectOfABA();
6 }
7
8 static void defectOfABA() {
9 final AtomicInteger atomicInteger = new AtomicInteger(1);
10
11 Thread coreThread = new Thread(
12 () -> {
13 final int currentValue = atomicInteger.get();
14 System.out.println(Thread.currentThread().getName() + " ------ currentValue=" + currentValue);
15
16 // 这段目的:模拟处理其他业务花费的时间
17 try {
18 Thread.sleep(300);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22
23 boolean casResult = atomicInteger.compareAndSet(1, 2);
24 System.out.println(Thread.currentThread().getName()
25 + " ------ currentValue=" + currentValue
26 + ", finalValue=" + atomicInteger.get()
27 + ", compareAndSet Result=" + casResult);
28 }
29 );
30 coreThread.start();
31
32 // 这段目的:为了让 coreThread 线程先跑起来
33 try {
34 Thread.sleep(100);
35 } catch (InterruptedException e) {
36 e.printStackTrace();
37 }
38
39 Thread amateurThread = new Thread(
40 () -> {
41 int currentValue = atomicInteger.get();
42 boolean casResult = atomicInteger.compareAndSet(1, 2);
43 System.out.println(Thread.currentThread().getName()
44 + " ------ currentValue=" + currentValue
45 + ", finalValue=" + atomicInteger.get()
46 + ", compareAndSet Result=" + casResult);
47
48 currentValue = atomicInteger.get();
49 casResult = atomicInteger.compareAndSet(2, 1);
50 System.out.println(Thread.currentThread().getName()
51 + " ------ currentValue=" + currentValue
52 + ", finalValue=" + atomicInteger.get()
53 + ", compareAndSet Result=" + casResult);
54 }
55 );
56 amateurThread.start();
57 }
58}
59```
60
61输出内容如下:
Thread-0 —— currentValue=1 Thread-1 —— currentValue=1, finalValue=2, compareAndSet Result=true Thread-1 —— currentValue=2, finalValue=1, compareAndSet Result=true Thread-0 —— currentValue=1, finalValue=2, compareAndSet Result=true ```
下面我们来详细介绍一下这些原子类。
2 基本类型原子类
2.1 基本类型原子类介绍
使用原子的方式更新基本类型
- AtomicInteger:整型原子类
- AtomicLong:长整型原子类
- AtomicBoolean :布尔型原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicInteger 为例子来介绍。
AtomicInteger 类常用方法
1public final int get() //获取当前的值
2public final int getAndSet(int newValue)//获取当前的值,并设置新的值
3public final int getAndIncrement()//获取当前的值,并自增
4public final int getAndDecrement() //获取当前的值,并自减
5public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
6boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
7public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
8```
9
10#### 2.2 AtomicInteger 常见方法使用
11
12```java
13import java.util.concurrent.atomic.AtomicInteger;
14
15public class AtomicIntegerTest {
16
17 public static void main(String[] args) {
18 // TODO Auto-generated method stub
19 int temvalue = 0;
20 AtomicInteger i = new AtomicInteger(0);
21 temvalue = i.getAndSet(3);
22 System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:0; i:3
23 temvalue = i.getAndIncrement();
24 System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:3; i:4
25 temvalue = i.getAndAdd(5);
26 System.out.println("temvalue:" + temvalue + "; i:" + i);//temvalue:4; i:9
27 }
28
29}
30```
31
32#### 2.3 基本数据类型原子类的优势
33
34通过一个简单例子带大家看一下基本数据类型原子类的优势
35
36**①多线程环境不使用原子类保证线程安全(基本数据类型)**
37
38```java
39class Test {
40 private volatile int count = 0;
41 //若要线程安全执行执行count++,需要加锁
42 public synchronized void increment() {
43 count++;
44 }
45
46 public int getCount() {
47 return count;
48 }
49}
50```
51**②多线程环境使用原子类保证线程安全(基本数据类型)**
52
53```java
54class Test2 {
55 private AtomicInteger count = new AtomicInteger();
56
57 public void increment() {
58 count.incrementAndGet();
59 }
60 //使用AtomicInteger之后,不需要加锁,也可以实现线程安全。
61 public int getCount() {
62 return count.get();
63 }
64}
2.4 AtomicInteger 线程安全原理简单分析
AtomicInteger 类的部分源码:
1 // setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用)
2 private static final Unsafe unsafe = Unsafe.getUnsafe();
3 private static final long valueOffset;
4
5 static {
6 try {
7 valueOffset = unsafe.objectFieldOffset
8 (AtomicInteger.class.getDeclaredField("value"));
9 } catch (Exception ex) { throw new Error(ex); }
10 }
11
12 private volatile int value;
AtomicInteger 类主要利用 CAS (compare and swap) + volatile 和 native 方法来保证原子操作,从而避免 synchronized 的高开销,执行效率大为提升。
CAS的原理是拿期望的值和原本的一个值作比较,如果相同则更新成新的值。UnSafe 类的 objectFieldOffset() 方法是一个本地方法,这个方法是用来拿到“原来的值”的内存地址。另外 value 是一个volatile变量,在内存中可见,因此 JVM 可以保证任何时刻任何线程总能拿到该变量的最新值。
3 数组类型原子类
3.1 数组类型原子类介绍
使用原子的方式更新数组里的某个元素
- AtomicIntegerArray:整形数组原子类
- AtomicLongArray:长整形数组原子类
- AtomicReferenceArray :引用类型数组原子类
上面三个类提供的方法几乎相同,所以我们这里以 AtomicIntegerArray 为例子来介绍。
AtomicIntegerArray 类常用方法
1public final int get(int i) //获取 index=i 位置元素的值
2public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
3public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
4public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
5public final int getAndAdd(int delta) //获取 index=i 位置元素的值,并加上预期的值
6boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
7public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。
8```
9#### 3.2 AtomicIntegerArray 常见方法使用
10
11```java
12
13import java.util.concurrent.atomic.AtomicIntegerArray;
14
15public class AtomicIntegerArrayTest {
16
17 public static void main(String[] args) {
18 // TODO Auto-generated method stub
19 int temvalue = 0;
20 int[] nums = { 1, 2, 3, 4, 5, 6 };
21 AtomicIntegerArray i = new AtomicIntegerArray(nums);
22 for (int j = 0; j < nums.length; j++) {
23 System.out.println(i.get(j));
24 }
25 temvalue = i.getAndSet(0, 2);
26 System.out.println("temvalue:" + temvalue + "; i:" + i);
27 temvalue = i.getAndIncrement(0);
28 System.out.println("temvalue:" + temvalue + "; i:" + i);
29 temvalue = i.getAndAdd(0, 5);
30 System.out.println("temvalue:" + temvalue + "; i:" + i);
31 }
32
33}
34```
35
36### 4 引用类型原子类
37
38#### 4.1 引用类型原子类介绍
39
40基本类型原子类只能更新一个变量,如果需要原子更新多个变量,需要使用 引用类型原子类。
41
42- AtomicReference:引用类型原子类
43- AtomicStampedReference:原子更新引用类型里的字段原子类
44- AtomicMarkableReference :原子更新带有标记位的引用类型
45
46上面三个类提供的方法几乎相同,所以我们这里以 AtomicReference 为例子来介绍。
47
48#### 4.2 AtomicReference 类使用示例
49
50```java
51import java.util.concurrent.atomic.AtomicReference;
52
53public class AtomicReferenceTest {
54
55 public static void main(String[] args) {
56 AtomicReference<Person> ar = new AtomicReference<Person>();
57 Person person = new Person("SnailClimb", 22);
58 ar.set(person);
59 Person updatePerson = new Person("Daisy", 20);
60 ar.compareAndSet(person, updatePerson);
61
62 System.out.println(ar.get().getName());
63 System.out.println(ar.get().getAge());
64 }
65}
66
67class Person {
68 private String name;
69 private int age;
70
71 public Person(String name, int age) {
72 super();
73 this.name = name;
74 this.age = age;
75 }
76
77 public String getName() {
78 return name;
79 }
80
81 public void setName(String name) {
82 this.name = name;
83 }
84
85 public int getAge() {
86 return age;
87 }
88
89 public void setAge(int age) {
90 this.age = age;
91 }
92
93}
94```
95上述代码首先创建了一个 Person 对象,然后把 Person 对象设置进 AtomicReference 对象中,然后调用 compareAndSet 方法,该方法就是通过通过 CAS 操作设置 ar。如果 ar 的值为 person 的话,则将其设置为 updatePerson。实现原理与 AtomicInteger 类中的 compareAndSet 方法相同。运行上面的代码后的输出结果如下:
Daisy 20 ```
4.3 AtomicStampedReference 类使用示例
1import java.util.concurrent.atomic.AtomicStampedReference;
2
3public class AtomicStampedReferenceDemo {
4 public static void main(String[] args) {
5 // 实例化、取当前值和 stamp 值
6 final Integer initialRef = 0, initialStamp = 0;
7 final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp);
8 System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
9
10 // compare and set
11 final Integer newReference = 666, newStamp = 999;
12 final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp);
13 System.out.println("currentValue=" + asr.getReference()
14 + ", currentStamp=" + asr.getStamp()
15 + ", casResult=" + casResult);
16
17 // 获取当前的值和当前的 stamp 值
18 int[] arr = new int[1];
19 final Integer currentValue = asr.get(arr);
20 final int currentStamp = arr[0];
21 System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);
22
23 // 单独设置 stamp 值
24 final boolean attemptStampResult = asr.attemptStamp(newReference, 88);
25 System.out.println("currentValue=" + asr.getReference()
26 + ", currentStamp=" + asr.getStamp()
27 + ", attemptStampResult=" + attemptStampResult);
28
29 // 重新设置当前值和 stamp 值
30 asr.set(initialRef, initialStamp);
31 System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
32
33 // [不推荐使用,除非搞清楚注释的意思了] weak compare and set
34 // 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]
35 // 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,
36 // so is only rarely an appropriate alternative to compareAndSet."
37 // todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发
38 final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp);
39 System.out.println("currentValue=" + asr.getReference()
40 + ", currentStamp=" + asr.getStamp()
41 + ", wCasResult=" + wCasResult);
42 }
43}
44```
45
46输出结果如下:
47```
48currentValue=0, currentStamp=0
49currentValue=666, currentStamp=999, casResult=true
50currentValue=666, currentStamp=999
51currentValue=666, currentStamp=88, attemptStampResult=true
52currentValue=0, currentStamp=0
53currentValue=666, currentStamp=999, wCasResult=true
54```
55
56#### 4.4 AtomicMarkableReference 类使用示例
57
58``` java
59import java.util.concurrent.atomic.AtomicMarkableReference;
60
61public class AtomicMarkableReferenceDemo {
62 public static void main(String[] args) {
63 // 实例化、取当前值和 mark 值
64 final Boolean initialRef = null, initialMark = false;
65 final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark);
66 System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
67
68 // compare and set
69 final Boolean newReference1 = true, newMark1 = true;
70 final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1);
71 System.out.println("currentValue=" + amr.getReference()
72 + ", currentMark=" + amr.isMarked()
73 + ", casResult=" + casResult);
74
75 // 获取当前的值和当前的 mark 值
76 boolean[] arr = new boolean[1];
77 final Boolean currentValue = amr.get(arr);
78 final boolean currentMark = arr[0];
79 System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);
80
81 // 单独设置 mark 值
82 final boolean attemptMarkResult = amr.attemptMark(newReference1, false);
83 System.out.println("currentValue=" + amr.getReference()
84 + ", currentMark=" + amr.isMarked()
85 + ", attemptMarkResult=" + attemptMarkResult);
86
87 // 重新设置当前值和 mark 值
88 amr.set(initialRef, initialMark);
89 System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
90
91 // [不推荐使用,除非搞清楚注释的意思了] weak compare and set
92 // 困惑!weakCompareAndSet 这个方法最终还是调用 compareAndSet 方法。[版本: jdk-8u191]
93 // 但是注释上写着 "May fail spuriously and does not provide ordering guarantees,
94 // so is only rarely an appropriate alternative to compareAndSet."
95 // todo 感觉有可能是 jvm 通过方法名在 native 方法里面做了转发
96 final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1);
97 System.out.println("currentValue=" + amr.getReference()
98 + ", currentMark=" + amr.isMarked()
99 + ", wCasResult=" + wCasResult);
100 }
101}
102```
103
104输出结果如下:
105```
106currentValue=null, currentMark=false
107currentValue=true, currentMark=true, casResult=true
108currentValue=true, currentMark=true
109currentValue=true, currentMark=false, attemptMarkResult=true
110currentValue=null, currentMark=false
111currentValue=true, currentMark=true, wCasResult=true
112```
113
114### 5 对象的属性修改类型原子类
115
116#### 5.1 对象的属性修改类型原子类介绍
117
118如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改类型原子类。
119
120- AtomicIntegerFieldUpdater:原子更新整形字段的更新器
121- AtomicLongFieldUpdater:原子更新长整形字段的更新器
122- AtomicStampedReference :原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用 CAS 进行原子更新时可能出现的 ABA 问题。
123
124要想原子地更新对象的属性需要两步。第一步,因为对象的属性修改类型原子类都是抽象类,所以每次使用都必须使用静态方法 newUpdater()创建一个更新器,并且需要设置想要更新的类和属性。第二步,更新的对象属性必须使用 public volatile 修饰符。
125
126上面三个类提供的方法几乎相同,所以我们这里以 `AtomicIntegerFieldUpdater`为例子来介绍。
127
128#### 5.2 AtomicIntegerFieldUpdater 类使用示例
129
130```java
131import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
132
133public class AtomicIntegerFieldUpdaterTest {
134 public static void main(String[] args) {
135 AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
136
137 User user = new User("Java", 22);
138 System.out.println(a.getAndIncrement(user));// 22
139 System.out.println(a.get(user));// 23
140 }
141}
142
143class User {
144 private String name;
145 public volatile int age;
146
147 public User(String name, int age) {
148 super();
149 this.name = name;
150 this.age = age;
151 }
152
153 public String getName() {
154 return name;
155 }
156
157 public void setName(String name) {
158 this.name = name;
159 }
160
161 public int getAge() {
162 return age;
163 }
164
165 public void setAge(int age) {
166 this.age = age;
167 }
168
169}
170```
171
172输出结果:
22 23 ```