Skip to content

Commit d8285f3

Browse files
committed
add post
1 parent ebc4396 commit d8285f3

File tree

4 files changed

+221
-1
lines changed

4 files changed

+221
-1
lines changed

db.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
title: Java并发编程(一)CAS
2+
tags:
3+
- CAS
4+
- 并发
5+
categories:
6+
- Dev
7+
- Java
8+
date: 2020-04-20 08:06:00
9+
cover: true
10+
---
11+
![](https://cdn.jsdelivr.net/gh/coder-lida/CDN/img/Jennifer-2.png)
12+
<!-- more -->
13+
## CAS 是什么
14+
CAS 的全称 Compare-And-Swap,它是一条 CPU 并发。
15+
16+
它的功能是判断内存某一个位置的值是否为预期,如果是则更改这个值,这个过程就是原子的。
17+
18+
CAS 并发原体现在 JAVA 语言中就是 `sun.misc.Unsafe` 类中的各个方法。调用 `UnSafe` 类中的 CAS 方法,JVM 会帮我们实现出 CAS 汇编指令。这是一种完全依赖硬件的功能,通过它实现了原子操作。由于 CAS 是一种系统源语,源语属于操作系统用语范畴,是由若干条指令组成,用于完成某一个功能的过程,并且原语的执行必须是连续的,在执行的过程中不允许被中断,也就是说 CAS 是一条原子指令,不会造成所谓的数据不一致的问题。
19+
20+
## 比较并交换
21+
CAS的意思就是比较并交换。上面说到,这个比较过程是原子的。我们新建一个测试类。
22+
```
23+
public class CASDemo {
24+
public static void main(String[] args) {
25+
checkCAS();
26+
}
27+
28+
public static void checkCAS(){
29+
AtomicInteger atomicInteger = new AtomicInteger(5);
30+
System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data is
31+
" + atomicInteger.get());
32+
System.out.println(atomicInteger.compareAndSet(5, 2020) + "\t current data is
33+
" + atomicInteger.get());
34+
}
35+
atomicInteger.getAndIncrement();
36+
System.out.println("current data is " + atomicInteger.get());
37+
}
38+
```
39+
查看返回结果
40+
```
41+
true current data is 2019
42+
false current data is 2019
43+
current data is 2020
44+
```
45+
原子整型类的初始值是5,当第一次调用compareAndSet的时候期望值是5,更新值是2019,此时的期望值和atomicInteger 值相等,则替换为更新值,输出为2019;第二次调用compareAndSet的时候期望值还是5,此时atomicInteger的值已经更新为2019,期望值和原始值不想等,不做更新操作,所以此时的atomicInteger值还是2019。
46+
47+
compareAndSet是AtomicInteger的一个方法
48+
```
49+
/**
50+
* Atomically sets the value to the given updated value
51+
* if the current value {@code ==} the expected value.
52+
*
53+
* @param expect the expected value
54+
* @param update the new value
55+
* @return {@code true} if successful. False return indicates that
56+
* the actual value was not equal to the expected value.
57+
*/
58+
public final boolean compareAndSet(int expect, int update) {
59+
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
60+
}
61+
```
62+
他调用的是unsafe类的`compareAndSwapInt`方法,this表示当前值对象,valueOffset是当前对象在内存中的偏移量,expect为期望值,update为更新值。
63+
64+
## 原子性
65+
需要说到 `atomicInteger.getAndIncrement();`这个方法,类似于i++。
66+
```
67+
/**
68+
* Atomically increments by one the current value.
69+
*
70+
* @return the previous value
71+
*/
72+
public final int getAndIncrement() {
73+
return unsafe.getAndAddInt(this, valueOffset, 1);
74+
}
75+
```
76+
也是调用的unsafe类的方法。
77+
来看一下`getAndAddInt`
78+
```
79+
public final int getAndAddInt(Object var1, long var2, int var4) {
80+
int var5;
81+
do {
82+
var5 = this.getIntVolatile(var1, var2);
83+
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
84+
85+
return var5;
86+
}
87+
```
88+
var1为当前对象,var2为当前对象在内存中的偏移量,var4为1,var5为`getIntVolatile(var1, var2)`的返回值,`getIntVolatile`方法的意思是当前对象var1且内存偏移量为var2时的值是多少。
89+
90+
在while循环中,同样调用了`compareAndSwapInt`方法,此时的var5为期望值,var5+var4为更新值。直到比较成功。
91+
92+
## Unsafe类
93+
94+
unsafe类是CAS的核心类,由于java无法直接访问底层系统,需要通过本地(native)方法来访问,基于unsafe类可直接操作特定内存的数据unsafe类存在于sun.mics包中,其内部方法可以像c的指针一样直接操作内存。因为 Java 中 CAS 操作执行依赖于 Unsafe 类。
95+
96+
变量 vauleOffset,表示该变量值在内存中的偏移量,因为 Unsafe 就是根据内存偏移量来获取数据的。
97+
98+
变量 value 用 volatile 修饰,保证了多线程之间的内存可见性。
99+
```
100+
public class AtomicInteger extends Number implements java.io.Serializable {
101+
private static final long serialVersionUID = 6214790243416807050L;
102+
103+
// setup to use Unsafe.compareAndSwapInt for updates
104+
private static final Unsafe unsafe = Unsafe.getUnsafe();
105+
private static final long valueOffset;
106+
107+
static {
108+
try {
109+
// 获取下面 value 的地址偏移量
110+
valueOffset = unsafe.objectFieldOffset
111+
(AtomicInteger.class.getDeclaredField("value"));
112+
} catch (Exception ex) { throw new Error(ex); }
113+
}
114+
115+
private volatile int value;
116+
// ...
117+
}
118+
```
119+
120+
## CAS 的缺点
121+
122+
123+
* 循环时间长开销很大
124+
* 如果 CAS 失败,会一直尝试,如果 CAS 长时间一直不成功,可能会给 CPU 带来很大的开销(比如线程数很多,每次比较都是失败,就会一直循环),所以希望是线程数比较小的场景。
125+
* 只能保证一个共享变量的原子操作
126+
* 对于多个共享变量操作时,循环 CAS 就无法保证操作的原子性。
127+
* 引出 ABA 问题
128+
129+
## ABA 问题
130+
131+
### 原子引用
132+
```
133+
public class AtomicRefrenceDemo {
134+
public static void main(String[] args) {
135+
User z3 = new User("张三", 22);
136+
User l4 = new User("李四", 23);
137+
AtomicReference<User> atomicReference = new AtomicReference<>();
138+
atomicReference.set(z3);
139+
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
140+
System.out.println(atomicReference.compareAndSet(z3, l4) + "\t" + atomicReference.get().toString());
141+
}
142+
}
143+
144+
@Getter
145+
@ToString
146+
@AllArgsConstructor
147+
class User {
148+
String userName;
149+
int age;
150+
}
151+
```
152+
153+
### ABA 问题是怎么产生的
154+
```
155+
public class ABADemo {
156+
private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
157+
158+
public static void main(String[] args) {
159+
new Thread(() -> {
160+
atomicReference.compareAndSet(100, 101);
161+
atomicReference.compareAndSet(101, 100);
162+
}).start();
163+
164+
new Thread(() -> {
165+
// 保证上面线程先执行
166+
try {
167+
Thread.sleep(1000);
168+
} catch (InterruptedException e) {
169+
e.printStackTrace();
170+
}
171+
atomicReference.compareAndSet(100, 2019);
172+
System.out.println(atomicReference.get()); // 2019
173+
}).start();
174+
}
175+
}
176+
```
177+
当有一个值从 A 改为 B 又改为 A,这就是 ABA 问题。
178+
179+
### ABA 问题解决
180+
时间戳原子引用
181+
```
182+
public class ABADemo2 {
183+
private static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
184+
185+
public static void main(String[] args) {
186+
new Thread(() -> {
187+
int stamp = atomicStampedReference.getStamp();
188+
System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp);
189+
try {
190+
Thread.sleep(1000);
191+
} catch (InterruptedException e) {
192+
e.printStackTrace();
193+
}
194+
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 );
195+
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1 );
196+
}).start();
197+
198+
new Thread(() -> {
199+
int stamp = atomicStampedReference.getStamp();
200+
System.out.println(Thread.currentThread().getName() + " 的版本号为:" + stamp);
201+
try {
202+
Thread.sleep(3000);
203+
} catch (InterruptedException e) {
204+
e.printStackTrace();
205+
}
206+
boolean b = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
207+
System.out.println(b); // false
208+
System.out.println(atomicStampedReference.getReference()); // 100
209+
}).start();
210+
}
211+
}
212+
```
213+
输出结果
214+
```
215+
Thread-0 的版本号为:1
216+
Thread-1 的版本号为:1
217+
false
218+
100
219+
```
220+

source/images/pasted-0.png

1.54 KB
Loading

source/images/pasted-1.png

1.54 KB
Loading

0 commit comments

Comments
 (0)