zoukankan      html  css  js  c++  java
  • 【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】这里加锁,从而减小了锁的粒度,以提高性能。
  • 相关阅读:
    属性值和引号
    PPT中背景音乐与插入文件中的声音有什么区别?
    C#笔记分享
    为什么《穹顶之下》没有说服我?
    Office 2019安装并激活(最简洁的安装方法)
    【转】获取Sprite的实际Rect
    什么是Cocos2d-x
    【转】最新基于adt-bundle-windows-x86的android开发环境搭建
    在cocos2d-x中使用位图字体
    TexturePacker的使用
  • 原文地址:https://www.cnblogs.com/boluopabo/p/12939284.html
Copyright © 2011-2022 走看看