欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

CAS无锁策略

程序员文章站 2022-12-17 14:58:06
在并发编程中,对于共享资源的使用需要确保绝对的安全性。除了利用锁机制之外,还有一种无锁的概念。所谓无锁,就是假定在并发情况下,对于共享资源的访问没有冲突,线程可以一直不停的运行,无需阻塞,如果产生冲突,则使用CAS算法确保安全性。Java在很多并发代码中都使用了这种算法。 CAS算法的核心参数如下: ......

  在并发编程中,对于共享资源的使用需要确保绝对的安全性。除了利用锁机制之外,还有一种无锁的概念。所谓无锁,就是假定在并发情况下,对于共享资源的访问没有冲突,线程可以一直不停的运行,无需阻塞,如果产生冲突,则使用cas算法确保安全性。java在很多并发代码中都使用了这种算法。

  cas算法的核心参数如下:

compareandset(v,e,a)

  v代码需要进行更新的变量;e代表预期值;a代表所要更新的值。

  cas的核心思想就是:当要对一个变量进行更新时,先取出该变量此时在内存中的实际值,与预期值进行比较。如果相等,则代表没有其他线程对其进行过修改,直接更新。如果不同,表示有其他线程修改过,此时有两种策略。一种是让cas自旋,直到更新成功;另外表示当前线程更新失败,继续后续逻辑。大概示意图如下:

CAS无锁策略

  对于cas自旋,有可能会出现长时间更新失败,浪费cpu性能的情况。jdk1.6以后,加入了适应性自旋的概念。即如果某个锁自旋时很少获得成功,那么会减少自旋次数。自旋次数的默认值是10次,可以使用参数-xx:preblockspin来更改。

  再说一下为什么cas在更新变量值的时候不会受到其他线程的影响呢?会不会我在判断更新值与预期值相等,进行更新的过程中,变量被其他线程更新了呢?其实不会,因为cas具有排它性,一次cas是一个原子操作,是cpu源语级别。确保隔离性。

  java对于cas是使用unsafe类进行支持的。顾名思义,不安全,可以让程序员像c语言那样直接操纵内存指针。java.util.concurrent.atomic包下的类,都是使用cas实现的。

 

aba缺陷

  不知道大家通过上面对cas的介绍说明,看到了cas算法的缺陷没有!

  cas本身会有aba问题。举个例子,变量m = 10,我要把m更新为30。在我判断之前,有其他线程先把m更新到50,在从50更新到10。那么我进行 10 == 10的判断时,这时候我怎么确定m的值有没有被改过呢?这就是aba问题了。

  要解决这个问题,我们就需要在进行 10 == 10判断的时候,还需要有其他参照。java中有一个类atomicstampedreference,这个类除了维护变量值之外,还会维护一个时间戳。用于确定数值版本。

private static class pair<t> {
        final t reference;
        final int stamp;
        private pair(t reference, int stamp) {
            this.reference = reference;
            this.stamp = stamp;
        }
        static <t> pair<t> of(t reference, int stamp) {
            return new pair<t>(reference, stamp);
        }
    }

    private volatile pair<v> pair;

  用volatile关键字修饰的内部类。

  

  看一下核心的compareandset()方法:

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)));
    }

  可以看到除了比较数值之外,还会比较时间戳。