CAS理解

了解过java的锁机制的朋友一定知道java的锁可以分为乐观锁和悲观锁,那么它们分别是什么意思呢?
 悲观锁:顾名思义,整体是很丧的。它认为在它对资源进行操作的时候,其他线程也会来对资源进行操作,因此一定要对线程进行一套管理机制,只有持有锁的线程才能对资源进行操作。
 乐观锁:整体积极向上。它认为它对资源进行操作的时候,其他线程不会对资源进行操作,因此不需要引入线程管理机制,不用对资源上锁。

CAScompare and swap,比较替换,就是典型的乐观锁机制
  CAS中主要用的有三个操作数:内存地址v,旧的预期值A,修改的新值B
  当我们更新一个变量的时候,只有当变量地址v的值是预期值A的时候,才会将内存地址v的对应值修改为B。

 举一个通俗的例子,假设我们存在AB两个舔狗同时想追求我们的一个女神,女神有一个牌子。我们约定当女神的牌子是0时,代表女神现在有空,可以约会,为1时代表女神没空不能约会。所以当AB看到女神有空时,都会去找女神约会,假设A抢先一步,找到了女神,并且将女神的牌子变成了1,此时B看到了女神的牌子变成了1,知道女神现在没空了,但是他也不会就此罢休,于是他不断等待,等待女神有空的时候,即自旋等待。
 但其实这也存在一个问题,就是AB都看到了女神是0,空闲状态。然后几乎同时到达,认为自己有资格和女神约会,将牌子翻为1。这和我们的设想不符,因此CAS还会引入volatile来进行协助。

volatile可以保证当主存更新后,所有线程中的私有内存全部失效,需要重新从主存中读取数据,然后进行CAS操作。

但是我们使用CAS时会遇见ABA的问题
即当我们想要修改的值在之前已经被改为了B,然后重新改为A,此时我们使用CAS是无法进行判断的。

public static void main(String[] args) {
        final AtomicInteger a = new AtomicInteger(1);
        Thread main = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("操作的线程:" + Thread.currentThread().getName() + " ,初始值: " + a.get());
                try {
                    int exepectNum = a.get();
                    int newNum = exepectNum + 1;
                    Thread.sleep(1000);
                    boolean b = a.compareAndSet(exepectNum, newNum);
                    System.out.println("操作的线程:" + Thread.currentThread().getName() + "操作是否成功:" + b);
                    System.out.println(a.get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "主线程");
        Thread other = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(20);
                    System.out.println("操作的线程:" + Thread.currentThread().getName() + "操作后的a值:" + a.incrementAndGet());
                    System.out.println("-------------------------------------------------");
                    System.out.println("操作的线程:" + Thread.currentThread().getName() + "操作后的a值:" + a.decrementAndGet());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        main.start();
        other.start();
    }

如上述代码所示,我们的执行结果是这样的
image.png

即我们在对a进行cas操作之前,已经对a进行了替换,但是却依然替换成功,证明我们使用cas操作存在ABA问题。
那么对于这个问题如何解决呢?

  1. 我们可以使用sychornized关键字进行修饰
  2. 我们可以使用compareAndSet 带版本号的方式进行解决,即AtomicStampedReference的方法进行解决
public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值