CAS机制

                                                                            CAS机制

   在说CAS之前,先让我们看一个简单的多线程例子:

public class Test {

    public static int num = 0;
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    }catch (Exception e){

                    }
                    for (int j=0;j<50;j++){
                        num++;
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        }catch (Exception e){

        }
        System.out.println(num);
    }
}

我们启动3个线程,每个线程里让count变量自增50次,按照正常情况最后打印出的num应该是3*50=150,但是在多线程情况下我们得到的不一定是150。

这个是因为num++操作不是原子性的,这样在多线程情况下就会出现问题,遇到这种情况我们最先想到的就是加锁:synchronized,最终我们每次执行都会得到正确结果150,如下代码:

for (int j=0;j<50;j++){
     synchronized (Test.class){
          num++;
     }
}

但是我们都知道synchronized采用的是悲观锁,也就是只能有一个线程能访问到当前代码块,其他没有获取到锁的线程都会在block状态,知道获取到锁,然后转成runable状态,代价比较高。

其实针对于这个例子我们可以采用一种更优的方式来实现:concurrent并发包里的原子类-AtomicInteger,下面我们用AtomicInteger来实现以下上面的功能:

public class Test {

    public static AtomicInteger num = new AtomicInteger(0);
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10);
                    }catch (Exception e){

                    }
                    for (int j = 0; j < 50; j++) {
                        num.incrementAndGet();
                    }
                }
            }).start();
        }
        try {
            Thread.sleep(1000);
        }catch (Exception e){

        }
        System.out.println(num);
    }
}

运行上面的代码,我们发现每次都是我们预期的值:为什么会有这种效果呢,真实因为原子类的底层采用的就是CAS机制。

 CAS:是Compare And Swap的缩写,意思是比较并且替换。

CAS机制当中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B。

更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

我们看一下incrementAndGet方法:

public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }
public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

我们发现底层其实调用的是:unsafe的compareAndSwapInt方法。

      CAS也有自己的缺点:由于采用的是乐观锁策略,当命中的时候他会一直重试,这样的话会消耗大量CPU资源,并且CAS只能用于单个变量的原子性,不可以采用在多变量同时需要原子性操作的场景,最大的一个问题是CAS会存在ABA问题。

     ABA:一个变量的值从A变成了B,又从B变成了A。

     假如有个变量的值为A,我们有3个线程同时操作:1.把A变成B   期望最后值为B

                                                                            2.把A变成B   期望最后值为B

                                                                            3.期望最后值为A

     假如,线程1先来,把A改成了B,线程2被阻塞了,线程3又把值改成了A,这个时候线程2恢复的话,就会把值改成B,但是按我们的要求应该是无法修改的,因为获取到的A已经不是之前的A了,是被修改过的值。举个例子

     此时操作一个单向链表堆栈,栈顶为A,A.next=B,线程1想将栈顶A换成B,在这个时候线程2来了,把A,B都pop出去了,然后重新push进了C,D,A,此时B处于游离状态,这个时候线程1开始修改栈顶,发现栈顶还是A,符合CAS原则,把A换成了B,但是由于B的next是null,所以无缘无故的就把C,D给丢掉了。

     ABA解决办法:版本号比较,在进行预期值A和实际值作比较的时候,加上一个版本号的比较,每次操作的时候版本号都会+1,只有同时符合这两个条件才可以进行操作。

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

举个例子:

public class ABATest {
    private static AtomicInteger num = new AtomicInteger(10);
    private static AtomicStampedReference num2 = new AtomicStampedReference(10,0);
    public static void main(String[] args) {
        casAtomicInteger();
        casAtomicStampedReference();
    }

    public static void casAtomicInteger(){
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                num.compareAndSet(10,11);
                num.compareAndSet(11,10);
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                }catch (Exception e){

                }
                boolean flag = num.compareAndSet(10,11);
                System.out.println(flag);
            }
        });
        thread1.start();
        thread2.start();
    }


    public static void casAtomicStampedReference(){
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10);
                }catch (Exception e){

                }
                num2.compareAndSet(10,11,num2.getStamp(),num2.getStamp()+1);
                num2.compareAndSet(11,10,num2.getStamp(),num2.getStamp()+1);
            }
        });
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = num2.getStamp();
                System.out.println("before stamp=="+stamp);
                try {
                    Thread.sleep(100);
                }catch (Exception e){

                }
                System.out.println("after stamp=="+num2.getStamp());
                boolean flag2 = num2.compareAndSet(10,11,stamp,stamp+1);
                System.out.println(flag2);
            }
        });
        thread1.start();
        thread2.start();
    }
}

结果:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值