Java并发基石--CAS原理

CAS(compare and swap,即比较并交换)
首先,我们引入一个需求。
假设我们开发一个网站,需要对访问量进行统计,用户每发送一次请求,访问量+1,如何实现?
我们模拟有100个人同时访问,并且每个人对咱们的网站发起10次请求,最后总访问的次数应该是1000次。

通过代码进行相关设计:

 static int count = 0;

    public static void request() throws InterruptedException{
    
    
        // 模拟耗时5毫毛
        TimeUnit.MILLISECONDS.sleep(5);
        count ++;
    }

    public static void main(String[] args) throws InterruptedException {
    
    
        long startTime = System.currentTimeMillis();
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);

        for(int i =0; i< threadSize;i++){
    
    
            Thread thread = new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                   // 模拟用户行为,每个用户访问10次网站
                   try{
    
    
                       for(int j =0;j<10; j++){
    
    
                           request();
                       }
                   }catch (Exception e) {
    
    
                       e.printStackTrace();
                   }finally {
    
    
                       countDownLatch.countDown();
                   }
                }
            });
            thread.start();
        }
       countDownLatch.await();
       long endTime = System.currentTimeMillis();
       System.out.println(Thread.currentThread().getName()+"耗时:"+(endTime - startTime)+",count ="+count);

    }

在这里插入图片描述

通过这段代码执行,看似没问题。我们执行一下,就会发现了。
在这里插入图片描述
显然,这个结果并不是我们预期的1000. 那么问题出现在什么地方了呢?
分析如下:
count++这个操作实际上由3步操作完成(具体见jvm相关知识)
1.获取count的值,记做A:A=count
2.将A的值+1,得到B: B=A+1;
3.将B赋值给count; count =B;
如果有A,B两个线程同时执行count++,他们通知执行到上面步骤的第一步,得到的count是一样的,3步操作结束后,count只加1,导致count结果不正确。

那么,我们知道了问题。怎么解决这个不正确的问题呢?
对Count++操作的时候,我们让多个线程排队处理,多个线程同时到达request()方法的时候,只能允许一个线程进去操作,其他线程必须在外面等待,等里面的处理完毕出来之后,外面等着的才可以进去,这样操作,count++就是排队进行的,结果一定正确。

接下来,我们就只需要考虑怎么实现队列效果了。
这个在Java中已经帮我们实现了。synchorinzed关键字和ReentrantLock都可以实现对资源的枷锁,保证并发正确性,多线程的情况下可以保证被锁住的资源被“串行”访问。

在request方法中使用了synchronized关键字修饰,保证了并发情况,request方法同一时刻只允许一个线程进入,request枷锁相当于串行执行了,count的结果和我们预期的一致。
在这里插入图片描述

但是呢,这个方式,却耗时太长了。
在这里插入图片描述

好了,接下来,我们就处理耗时问题。进行“锁升级”方式处理。
很显然,我们并不需要对整个request()方法进行锁资源,只需要对++这个操作进行锁就够了。
第三步的实现升级为:
1.获取锁;
2.获取以下count的值,记做LV
3.判断LV是否和A相等,如果相等,将B的值赋值给count,并返回true,否则返回false;
4.释放锁

实现如下:

 volatile static int count = 0;

    public static void request() throws InterruptedException{
    
    
        // 模拟耗时5毫毛
        TimeUnit.MILLISECONDS.sleep(5);

        int expectCount; // 表示期望值
        while (!compareAndSwap((expectCount = getCount()),expectCount+1)){
    
    }
    }

    public static  int getCount(){
    
    
        return  count;
    }

    public  static synchronized  boolean compareAndSwap(int expectCount,int newCount){
    
    
        // 判断count当前值是否与期望值expectCount一致,如果一致,将newCount赋值给count
        if(getCount() == expectCount){
    
    
            count = newCount;
            return true;
        }
        return false;
    }
    public static void main(String[] args) throws InterruptedException {
    
    
        long startTime = System.currentTimeMillis();
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);

        for(int i =0; i< threadSize;i++){
    
    
            Thread thread = new Thread(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    // 模拟用户行为,每个用户访问10次网站
                    try{
    
    
                        for(int j =0;j<10; j++){
    
    
                            request();
                        }
                    }catch (Exception e) {
    
    
                        e.printStackTrace();
                    }finally {
    
    
                        countDownLatch.countDown();
                    }
                }
            });
            thread.start();
        }
        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"耗时:"+(endTime - startTime)+",count ="+count);

    }

这样,如图,我们就可以很快的获取到一个正确的值了。在这里插入图片描述

这里用到了CAS(compare and swap)原理
在这里插入图片描述

CAS原理:
CAS通过调用JNI的代码实现的吗,JNI:Java Native Interface,允许Java调用其他语言。而compareAndSwapxx系列的方法就是借助“C语言”来调用cpu底层指令实现的。

源码中,如下所示:
在这里插入图片描述

那么CAS是万能的么?没有任何问题了么?
不是的,CAS在使用的时候,会遇到ABA问题。
什么是ABA问题?
CAS需要在操作值的时候检查下值是否发生变化,如果没有发生变化,才更新。但是如果一个值原来是A,在CAS方法执行之前,被其他线程修改为了B,然后又修改回了A,那么CAS方法执行检查的时候会发现它的值没有发生改变,但实际上却变化了。这就是CAS的ABA问题。
关于如何解决ABA问题。下次进行详细的学习记录。

Guess you like

Origin blog.csdn.net/qq_35529931/article/details/113834389