CAS-ABA问题

InterviewCoder

# CAS-ABA 问题

# 一。概述:

​ ABA 问题是在多线程并发的情况下,发生的一种现象。上一次记录了有关 CAS 操作的一些知识,CAS 通过比较内存中的一个数据是否是预期值,如果是就将它修改成新值,如果不是则进行自旋,重复比较的操作,直到某一刻内存值等于预期值再进行修改。而 ABA 问题则是在 CAS 操作中存在的一个经典问题,这个问题某些时候不会带来任何影响,某些时候却是影响很大的。

# 二。什么是 ABA 问题?

# 理解一:

​ 当执行 campare and swap 会出现失败的情况。例如,一个线程先读取共享内存数据值 A,随后因某种原因,线程暂时挂起,同时另一个线程临时将共享内存数据值先改为 B,随后又改回为 A。随后挂起线程恢复,并通过 CAS 比较,最终比较结果将会无变化。这样会通过检查,这就是 ABA 问题。 在 CAS 比较前会读取原始数据,随后进行原子 CAS 操作。这个间隙之间由于并发操作,最终可能会带来问题。

# 理解二:

“ABA” 问题:假设 t1 线程工作时间为 10 秒,t2 线程工作时间为 2 秒,那么可能在 A 的工作期间,主内存中的共享变量 A 已经被 t2 线程修改了多次,只是恰好最后一次修改的值是该变量的初始值,虽然用 CAS 判定出来的结果是期望值,但是却不是原来那个了 =======》“狸猫换太子”
相当于是只关心共享变量的起始值和结束值,而不关心过程中共享变量是否被其他线程动过。
有些业务可能不需要关心中间过程,只要前后值一样就行,但是有些业务需求要求变量在中间过程不能被修改。

只靠 CAS 无法保证 ABA 问题,需要使用 “原子引用” 才能解决!!!!

# 三.ABA 问题的解决:

# 原子引用:(存在 ABA 问题)

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
 package InterviewTest;

import java.util.concurrent.atomic.AtomicReference;

class User{
String name;
int age;

public User(String name,int age) {
this.name=name;
this.age=age;
}

@Override
public String toString() {
return "User [name=" + name + ", age=" + age + "]";
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}
}
public class AtomicReferenceDemo {
public static void main(String[] args) {
User z3 = new User("z3",25);
User li4 = new User("li4",25);
AtomicReference<User> atomicReference = new AtomicReference<>();
atomicReference.set(z3);
System.out.println(atomicReference);
System.out.println(atomicReference.compareAndSet(z3, li4)+
" "+atomicReference.get().toString());
System.out.println(atomicReference.compareAndSet(li4, z3)+
" "+atomicReference.get().toString());
}
}

# 带版本号的原子引用(解决 ABA 问题)

AtomicStampedReference 版本号原子引用:
案例:两种原子引用的对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105

package InterviewTest;

import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo {


static AtomicReference<Integer> atomicReference
= new AtomicReference<>(100);
static AtomicStampedReference<Integer> atomicStampedReference
= new AtomicStampedReference<>(100,1);


public static void main(String[] args) {

System.out.println("************以下是ABA问题的产生**************");
new Thread(()->{
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
},"t1").start();

new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println(atomicReference.compareAndSet(100, 2019)
+" "+atomicReference.get());
},"t2").start();


try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

System.out.println("************以下是ABA问题的解决**************");

new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()
+" "+" 第一次版本号:"+stamp);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

atomicStampedReference.compareAndSet(100,
101,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()
+" "+" 第2次版本号:"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101,
100,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp()+1);
System.out.println(Thread.currentThread().getName()
+" "+" 第3次版本号:"+atomicStampedReference.getStamp());

},"t3").start();

new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName()
+" "+" 第一次版本号:"+stamp);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}

boolean result = atomicStampedReference.compareAndSet(
100,
2019,
stamp,
stamp+1);
System.out.println(Thread.currentThread().getName()+
" 修改成功否:"+result+" 当前最新实际版本号:"
+atomicStampedReference.getStamp());
System.out.println(Thread.currentThread().getName()+
" 当前实际最新值:"
+atomicStampedReference.getReference());

},"t4").start();
}

}

输出:
************以下是ABA问题的产生**************
true 2019
************以下是ABA问题的解决**************
t3 第一次版本号:1
t4 第一次版本号:1
t3 第2次版本号:2
t3 第3次版本号:3
t4 修改成功否:false 当前最新实际版本号:3
t4 当前实际最新值:100

# 关于我

Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!

InterviewCoder

非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!

评论