CAS中的ABA问题以及解决方案

ABA问题

在没有加版本号之前,CAS会出现ABA问题:当一个值原本已经被当前线程读取到,准备通过CAS(自旋锁)将其修改的时候,突然这个时候由于网络卡顿、线程中断等一系列状况的原因,中途来了另外一个线程,将当前线程所期望的值修改成其他的值,然后又修改回来,这期间当前线程没有察觉,看了下此时的值跟预取的值相同,就直接修改了。坦白说,这种情况如果不是在很严格的场景下,其实是无关紧要的,但是如果是涉及到银行系统的资金流转问题之类的就是相当严重了。所以说,ABA问题我们还是有必要解决的,而且要很好的解决。

存在ABA问题的代码

 1 package com.lzp.juc;
 2 
 3 import java.util.concurrent.atomic.AtomicInteger;
 4 
 5 /**
 6  * @Author LZP
 7  * @Date 2021/6/10 13:24
 8  * @Version 1.0
 9  */
10 public class ABA {
11 
12     static volatile AtomicInteger a = new AtomicInteger();
13 
14     public static void main(String[] args) {
15         // 模拟两个线程
16         /*
17             第一个线程去获取a的值,然后休眠两秒(即模拟时间片用完了,为的是让另一个线程去重现ABA现象)
18             一开始第二个线程需要休眠20ms,其目的是让第一个线程先将其a的值读到自己的栈空间,然后自己再
19             去操作a的值
20          */
21         Thread main = new Thread(() -> {
22             int exceptVal = a.get();
23             System.out.println(Thread.currentThread().getName() + "第一次获取到的值:" + exceptVal);
24             try {
25                 // 先休眠1s
26                 Thread.sleep(1000);
27             } catch (InterruptedException e) {
28                 e.printStackTrace();
29             }
30             // 然后再去获取a的值
31             System.out.println(Thread.currentThread().getName() + "是否修改成功:" + a.compareAndSet(exceptVal, exceptVal + 1));
32         }, "主线程");
33 
34         Thread ABA = new Thread(() -> {
35             // 先休眠2ms
36             try {
37                 Thread.sleep(2);
38             } catch (InterruptedException e) {
39                 e.printStackTrace();
40             }
41 
42             // 自增
43             a.incrementAndGet();
44             System.out.println(Thread.currentThread().getName() + "increment之后的值:" + a.get());
45             // 自减
46             a.decrementAndGet();
47             System.out.println(Thread.currentThread().getName() + "decrement之后的值:" + a.get());
48         }, "制造ABA问题的线程");
49 
50         // 启动线程
51         main.start();
52         ABA.start();
53     }
54 }

运行结果:

利用Java中AtomicStampedReference类解决ABA问题

 1 package com.lzp.juc;
 2 
 3 import java.util.concurrent.atomic.AtomicStampedReference;
 4 
 5 /**
 6  * @Author LZP
 7  * @Date 2021/6/10 13:20
 8  * @Version 1.0
 9  *
10  * CAS中的ABA问题解决
11  * 这里我们不能只用值来判断了,还需要有一个版本号来标识,此时我们就可以
12  * 用jdk中提供的一个类AtomicStampedReference
13  *
14  * 其中有一个私有的静态内部类
15  *  private static class Pair<T> {
16  *      final T reference;
17  *      final int stamp;
18  *      private Pair(T reference, int stamp) {
19  *          this.reference = reference;
20  *          this.stamp = stamp;
21  *      }
22  *      static <T> Pair<T> of(T reference, int stamp) {
23  *          return new Pair<T>(reference, stamp);
24  *      }
25  *  }
26  *
27  *  AtomicStampedReference与AtomicInteger进行对比
28  *      1、AtomicStampedReference类:
29  *      主线程第一次获取到的值:1
30  *      制造ABA问题的线程increment之后的值:2
31  *      制造ABA问题的线程decrement之后的值:1
32  *      主线程是否修改成功:false
33  *
34  *      2、AtomicInteger类:
35  *      主线程第一次获取到的值:0
36  *      制造ABA问题的线程increment之后的值:1
37  *      制造ABA问题的线程decrement之后的值:0
38  *      主线程是否修改成功:true
39  */
40 public class CASABA {
41 
42     static volatile AtomicStampedReference<Integer> a = new AtomicStampedReference<>(1, 1);
43 
44     public static void main(String[] args) {
45         // 模拟两个线程
46         /*
47             第一个线程去获取a的值,然后休眠1秒(即模拟时间片用完了,为的是让另一个线程去重现ABA现象)
48             一开始第二个线程需要休眠2ms,其目的是让第一个线程先将其a的值读到自己的栈空间,然后自己再
49             去操作a的值
50          */
51         Thread main = new Thread(() -> {
52             int exceptRef = a.getReference();
53             int exceptStamp = a.getStamp();
54             System.out.println(Thread.currentThread().getName() + "第一次获取到的值:" + exceptRef);
55             try {
56                 // 先休眠1s
57                 Thread.sleep(1000);
58             } catch (InterruptedException e) {
59                 e.printStackTrace();
60             }
61             // 然后再去获取a的值
62             System.out.println(Thread.currentThread().getName() + "是否修改成功:" + a.compareAndSet(exceptRef, exceptRef + 1, exceptStamp, exceptStamp + 1));
63             System.out.println("版本号Stamp:" + a.getStamp());
64         }, "主线程");
65 
66         Thread ABA = new Thread(() -> {
67             // 先休眠2ms
68             try {
69                 Thread.sleep(2);
70             } catch (InterruptedException e) {
71                 e.printStackTrace();
72             }
73 
74             // 自增
75             a.compareAndSet(a.getReference(), a.getReference() + 1, a.getStamp(), a.getStamp() + 1);
76             System.out.println(Thread.currentThread().getName() + "increment之后的值:" + a.getReference());
77             // 自减
78             a.compareAndSet(a.getReference(), a.getReference() - 1, a.getStamp(), a.getStamp() + 1);
79             System.out.println(Thread.currentThread().getName() + "decrement之后的值:" + a.getReference());
80         }, "制造ABA问题的线程");
81 
82         // 启动线程
83         main.start();
84         ABA.start();
85     }
86 
87 }

运行结果:

 

posted @ 2021-07-05 21:40  没有你哪有我  阅读(782)  评论(0编辑  收藏  举报