【Java】手把手模拟CAS,瞬间理解CAS的机制

  • 话不多少,先看个案例,【模拟100个用户,每个用户访问10次网站】”:
public class ThreadDemo1 {

    //总访问量
    private static int count = 0;

    //模拟访问的方法
    public static void request() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(5);//模拟耗时5s
        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 < 100; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //模拟用户行为,每个用户访问10次网站
                    try {
                        for (int j = 0; j < 10; j++) {
                            request();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            });
            thread.start();
        }


        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println("访问用时:" + (endTime - startTime) + "ms");
        System.out.println(count);
    }

}
#结果1:
访问用时:73ms 968
#结果2:
访问用时:78ms 983

总之,结果基本都不会达到1000

  • 分析一下问题出在哪呢?

【count++】 操作实际上是由三步来完成的(jvm执行引擎)

1》获取count的值,记做A: A = count

2》将A值+1,得到B:B=A+1

3》将B值赋给count

  • 怎么解决结果不正确的问题呢?
对count++操作的时候,我们让多个线程排队处理,多个线程同时到达处理【request()方法】时,只能允许有一个线程进去操作,其他线程只能在外面等待(即串行化),

等到里面的线程处理完毕后,再让外面等待的线程进去一个,这样操作结果一定是争取的。
  • 通常如何实现排队呢?
1》synchronized关键字加锁
 (详情可以了解《Synchronized底层加锁原理详解》:https://www.cnblogs.com/boluopabo/p/12907916.html

2》ReentrantLock可重入锁
  • 那我们试一下【synchronized】加锁后的执行结果:
    //我们试着在request方法前加一个synchronized 修饰

//模拟访问的方法 public synchronized static void request() throws InterruptedException { TimeUnit.MILLISECONDS.sleep(5);//模拟耗时5s count++; }
#结果:
访问用时:5884ms
1000

虽然解决了线程的不安全问题,但是用却多了几十倍。

  • 耗时太长的原因是什么呢?
程序中的request方法使用synchronized关键字修饰,保证了并发情况下,request方法同一时刻,只允许一个线程进入,

request相当于串行执行了,count结果与预期一致,但耗时太长了
  • 如何解决耗时太长问题呢?

文章最初我们简述了【count】的变化过程,这里我们再重复一遍,并延伸一下:

1》获取count的值,记做A: A = count

2》将A值+1,得到B:B=A+1

3》将B值赋给count

升级第三步的实现:
a.获取锁
b.获取以下count最新的值,记做LV
c.判断LV是否等于A,如果相等,则把B的值赋值给count,并返回true;否则返回false
d.释放锁
  • 我们这里模拟一下上述升级第三步的实现场景:
package com.example.demo.thread;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author Code Farmer
 * @date 2020/5/22 15:10
 */
public class ThreadDemo3 {

    //总访问量
    //此处加volatile修改,保证可见性
    private volatile static int count = 0;

    //模拟访问的方法
    public static void request() throws InterruptedException {
        TimeUnit.MILLISECONDS.sleep(5);//模拟耗时5s
//        count++;
        int expectCount;
        //a.获取锁
        //b.获取以下count最新的值,记做LV
        while (!compareAndSwap((expectCount = getCount()), expectCount + 1)) {

        }
    }

    /**
     * @param expectCount count期望值
     * @param newCount    需要给count赋予的新值
     * @return 交换成功返回true;反之返回false
     */
    public static synchronized boolean compareAndSwap(int expectCount, int newCount) {
//        c.判断LV是否等于A,如果相等,则把B的值赋值给count,并返回true;否则返回false
//        d.释放锁
        if (expectCount == getCount()) {
            count = newCount;
            return true;
        }
        return false;
    }

    private static int getCount() {
        return 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 < 100; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    //模拟用户行为,每个用户访问10次网站
                    try {
                        for (int j = 0; j < 10; j++) {
                            request();
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {
                        countDownLatch.countDown();
                    }
                }
            });
            thread.start();
        }


        countDownLatch.await();
        long endTime = System.currentTimeMillis();
        System.out.println("访问用时:" + (endTime - startTime) + "ms");
        System.out.println(count);
    }

}
#结果:
访问用时:78ms
1000

返回结果可以看到,性能可谓是爆炸式提升啊。

  • 小结:在模拟CAS机制中,我们只在【将B值赋给count】这里加锁,从而减小了锁的粒度,以提高性能。
原文地址:https://www.cnblogs.com/boluopabo/p/12939284.html