前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >并发编程中cas的这三大问题你知道吗?

并发编程中cas的这三大问题你知道吗?

作者头像
苏三说技术
发布2020-10-15 14:47:44
5830
发布2020-10-15 14:47:44
举报
文章被收录于专栏:苏三说技术

在java中cas真的无处不在,它的全名是compare and swap,即比较和交换。它不只是一种技术更是一种思想,让我们在并发编程中保证数据原子性,除了用锁之外还多了一种选择。

一、cas的思想

先用一张图了解一下cas的思想。

cas需要四个要素:地址、旧值、期望值 和 新值。

地址:就不用多说了,就是一个引用,可以通过这个引用找到相应的元素,图中没有给出地址,是因为太简单了。

旧值:就是元素中swap前的数据

期望值:其实跟旧值是一样的

新值:计算之后得到的新数据

二、jdk是如何实现cas的

jdk给我们封装了Unsafe类,它里面提供了很多native compareAndSwap开头的方法,我们可以直接使用这些方法实现cas的功能。

代码语言:javascript
复制
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

此外,还提供了getAndAdd开头的方法,其实就是对compareAndSwap开头的方法做了一次封装,

代码语言:javascript
复制
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
      var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    return var5;
}

public final long getAndAddLong(Object var1, long var2, long var4) {
    long var6;
    do {
      var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
    return var6;
}

public final int getAndSetInt(Object var1, long var2, int var4) {
    int var5;
    do {
      var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var4));
    return var5;
}

public final long getAndSetLong(Object var1, long var2, long var4) {
    long var6;
    do {
      var6 = this.getLongVolatile(var1, var2);
    } while(!this.compareAndSwapLong(var1, var2, var6, var4));
    return var6;
}

public final Object getAndSetObject(Object var1, long var2, Object var4) {
    Object var5;    do {
      var5 = this.getObjectVolatile(var1, var2);
    } while(!this.compareAndSwapObject(var1, var2, var5, var4));
    return var5;
}

它里面会循环调用compareAndSwap开头的方法,一直到成功为止。这里就是所谓的自旋,说白了是死循环的方式。

三、jdk中是如何使用cas的

jdk使用cas两个最典型的应用是atomic包 和 aqs(AbstractQueuedSynchronizer)

先看看AtomicInteger类

代码语言:javascript
复制
//递增方法
public final int getAndIncrement() {   
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

//递减方法
public final int getAndDecrement() {   
     return unsafe.getAndAddInt(this, valueOffset, -1);
}

它其实就是用了上面提到的Unsafe类的getAndAddInt()方法。

当然,这里使用Unsafe类的方法可以保证简单操作的原子性,如果要保证多线程的可见性,还需要使用volatile关键字一起配合使用。

所以,AtomicInteger类的value值需要这样定义:

代码语言:javascript
复制
private volatile int value;

再看看AbstractQueuedSynchronizer类,里面包含了compareAndSet开头的很多方法

代码语言:javascript
复制
private final boolean compareAndSetHead(Node update) {
    return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

private static final boolean compareAndSetWaitStatus(Node node,int expect,int update)
   return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
}

private static final boolean compareAndSetNext(Node node,Node expect,
                                  Node update)
    return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}

它也是用到了Unsafe类,不过它调用的是compareAndSwap开头的方法。

四、cas的三大问题

使用cas保证数据原子性相对于加锁来说确实是一个不错的办法,在JDK中也用得比较多,但是也有它的问题

1.ABA问题

比如线程1 和 线程2 同时获取到数据A,线程1先执行,把数据修改成了B,又修改成了A。此时线程2执行的时候比较后和之前的A相等,就直接交换成了新值。这种情况其实是不应该修改的,因为数据已经发生了变化。

当然直接使用Unsafe类自身是无法解决ABA问题的,那怎么办?

我们需要使用AtomicStampedReference类,增加版本号,比较的时候先比较版本号。

2.循环时间长

一般情况下使用cas要配合自旋(死循环)一起,如果高并发的时候,会出现有很多请求多次循环也成功不了的情况,给cpu带来非常大的消耗。

3.只能保证一个变量的原子性

cas可以保证原子性,但是只能保证一个变量的原子性。我们上面提到的atomic包 和 aqs,也都只能保证一个变量的原子性。因为cas的原子性是靠cpu执行指令的时候内部机制保证的,它只能一次保证比较和交换操作的原子性。

那么问题来了,如果要保证多个变量的原子性该怎么办呢?

可以把多个变量合并成一个变量,然后使用JDK 提供的 AtomicReference 类来保证引用对象之间的原子性,就可以把 多个变量放在一个对象里来进行 CAS 操作。

如果这篇文章对您有所帮助,或者有所启发的话,帮忙关注一下:苏三说技术,或者点赞,转发一下,坚持原创不易,您的支持是我前进最大的动力,谢谢?

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-10-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 苏三说技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、cas的思想
  • 二、jdk是如何实现cas的
  • 三、jdk中是如何使用cas的
  • 四、cas的三大问题
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档