JUC-CAS算法

参考文章:https://cloud.tencent.com/developer/article/1496220

CAS(Compare-And-Swap)(比较并交换)(无锁优化 自旋 乐观锁)

例如,一个线程在使用AtomicInteger原子变量进行修改值的操作中,底层就是CAS算法,CAS有个三参数:内存值V,预期值A,新值B,当且仅当预期值A和内存值V相同时,才会将内存值修改为新值B并返回true,否则什么都不做,并返回false

Unsafe类是CAS的核心类,Unsafe里有可以直接操作特定内存数据的方法,内部方法操作可以像C指针一样操作内存,所以CAS操作的执行依赖于Unsafe类的方法

缺点:

1.循环时间长开销大

如果CAS失败,会一直循环尝试,如果一直不成功,可能会给CPU带来很大开销

2.只能保证一个共享变量的原子操作

3.会出现ABA问题

解决ABA问题

加version,使用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
/**
* ABA问题解决
* @author wannengqingnian
*/
public class TestAtomicStampedReference {

/**
* 创建带时间戳的原子引用
*/
static AtomicStampedReference<Integer> atomicStampedReference
= new AtomicStampedReference<>(100, 1);

public static void main(String[] args) {

//启动一个T1线程模拟ABA问题出现
new Thread(() -> {
//获取时间戳
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次时间戳" + stamp);

//暂停1秒钟T1线程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}

//模拟ABA
atomicStampedReference.compareAndSet(100, 101,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第一次修改版本号 : "+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100,
atomicStampedReference.getStamp(),
atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次修改版本号 : "+atomicStampedReference.getStamp());

}, "T1").start();

//启动T2线程验证是否解决ABA问题
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t线程获得的版本号 :"+stamp);

//暂停3秒,确保T1完成ABA问题
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}

//开始修改
Boolean flag = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

System.out.println(Thread.currentThread().getName() + "\t修改是否成功"+flag + "\t此时的版本号" + atomicStampedReference.getStamp());

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

}