CAS

CAS概念:

  CAS号称是无锁优化,或者叫自旋。

我们先通过一个简单的例子来说明为什么要使用CAS。

public class T {

    private int count = 10;

    public void m() {
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void main(String[] args) {
        T t = new T();
        for (int i = 0; i < 10 ; i++) {
            new Thread(()->t.m()).start();
        }
    }

}
View Code

这段代码的执行结果如下:

 

 可以看到结果并不是我们想要的,我们可以通过加锁的方式去实现:

public class T {

    private int count = 10;

    public synchronized void m() {
            count--;
            System.out.println(Thread.currentThread().getName() + " count = " + count);
    }

    public static void main(String[] args) {
        T t = new T();
        for (int i = 0; i < 10 ; i++) {
            new Thread(()->t.m()).start();
        }
    }

}
View Code

结果如下:

 

 虽然我们实现了同步,但是加锁对系统资源的消耗是非常大的,所以java为我们提供了一些通过CAS实现原子变量类,

比如我们来看如下代码:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class T2 {

    private int count = 0;

    public void m() {
        count++;
    }

    public static void test(){
        T2 t = new T2();
        List<Thread> threadList = new ArrayList<>();

        for (int i = 0; i < 55000 ; i++) {
            threadList.add(new Thread(()->t.m()));
        }
        threadList.forEach(thread -> thread.start());

        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }

    public static void main(String[] args) {
        for (int i = 0; i <10 ; i++) {
            test();
        }
    }

}
View Code

执行结果如下:

 

 

 可以看到,我们通过多线程去操作一个共享变量,得到得结果并不是准确的,也就是说存在线程安全问题。当然我们可以用synchronized关键字来同步,但是比较笨重。

这里我们可以使用Atomic包下的AtomicInteger来实现同步,具体代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class T1 {

    private AtomicInteger count = new AtomicInteger(0);

    public void m() {
        count.incrementAndGet();
    }

    public static void  test(){
        T1 t = new T1();
        List<Thread> threadList = new ArrayList<>();

        for (int i = 0; i < 55000 ; i++) {
            threadList.add(new Thread(()->t.m()));
        }

        threadList.forEach(thread -> thread.start());

        threadList.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            test();
        }
    }

}
View Code

执行结果如下:

 

 可以看到我们没有加锁,但是同样达到了同步的目的。AtomicInteger的底层就是CAS实现的,那么CAS究竟是怎么实现的呢?

CAS好比一个代理人(中间人),共享同一变量V的线程就是他的客户,当客户需要更新变量V的值时,他们需要请求代理人代为

修改,为此,客户要告诉代理人其看到的共享变量的当前值A以及期望值B。CAS作为代理人,相当于如下伪代码所示的函数:

boolean compareAndSwap(Variable V,Object A,Object B){
    //如果线程看到的值和当前值A相等,则说明共享变量V没有被其他线程改变
    if(A==V.get()){
        V.set(B);//更新变量值
        return true;//更新成功
    }
    return false;//更新失败,继续请求代理人代为修改
  }

该操作的原子性由处理器保障,所以该操作不存在线程安全问题。

java.util.concurrent.atomic下的类的底层都是通过CAS来保障线程安全的,这里就不一一举例了。很多小伙伴会发现我们在实际

开发中,其实很少用到这些类,基本上大家都是用synchronized加锁。但是在超高并发的情况下,我们就要慎重选择了。但是我们

一定不要武断的认为synchronized效率一定比CAS低,现在synchronized有了锁升级的概念,在不是超高并发的情况下,真的并不

一定效率低于CAS。

 ABA问题:

  ABA问题其实很简单,我们回过头去看CAS的实现,当当前线程期望的值和共享变量相等的时候,我们就判定共享变量没有被修改,

但是真的是这样吗?其实不是的,如果在这期间共享变量A被一个线程修改为B,然后又被另一个线程修改为A,其实以及发生了改变,只是

又被改回了原来的值,当前线程无法得知而已。这就是ABA问题。

  那么如何规避ABA问题呢,也很简单,只需要在更新共享变量的的时候引入一个修订号,每次都增加1。例如A-B-A改为[A,0]-[A,1]-[A-1]。

这样我们就能准确的判断共享变量是否被其他线程修改过。AtomicStampedReference类就是基于这种思想产生的。

posted @ 2020-08-14 23:49  负重前行的小牛  阅读(319)  评论(0编辑  收藏  举报