带你尝试手写模拟 cas 及其应用场景

1 cas What?

cas是compareandswap的简称,从字面上理解就是比较并更新,简单来说:从某一内存上取值V,和预期值A进行比较,如果内存值V和预期值A的结果相等,那么我们就把新值B更新到内存,如果不相等,那么就重复上述操作直到成功为止。

2 引出 CAS

需求:
开发一个网站,对访问量进行统计,用户每发送一个请求,访问量+1,如何实现?
模拟100个用户同时访问,并且每个人对咱们的网站发起10次请求,最后的总访问次数100*10=1000

2.1 示例1(线程不安全,结果不正确)

代码

public class CAS01 {
    // 总访问量
    static int count = 0;

    // 模拟访问的方法
    public static void request() throws InterruptedException {
        // 模拟耗时5毫秒
        TimeUnit.MILLISECONDS.sleep(5);
        count++;
    }


    public static void main(String[] args) throws InterruptedException {
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        // 开始时间
        Long startTime = System.currentTimeMillis();
        // 模拟100个用户
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                // 每个线程就是一个用户
                // 模拟每个用户发起十次访问请求
                try {
                    for (int j = 0; j < 10; j++) {
                        request();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 每个线程结束之后 threadSize -1
                    countDownLatch.countDown();
                }

            });
            thread.start();
        }
        // 保证100个线程结束之后,再执行后面的代码
        countDownLatch.await(); // threadSize==0 执行后面的代码
        Long endTime = System.currentTimeMillis();
        System.out.println("Time: " + (endTime - startTime) + ", count: " + count);
    }
}

结果

image-20210329205540052

分析

Q: 为什么count!=1000,问题出在哪里了?

A: count++ 操作实际上是由3步来完成

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

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

3.将B的值赋值给 count

如果有两个线程同时执行 count++,两个线程执行到上面步骤第一步,得到的count 的值是一样的,3步操作结束后,count 只有+1,导致count结果不正确!

Q: 怎么解决结果不正确问题?
A: 对 count++ 操作的时候,我们让多个线程进行排队,多个线程同时到达 request() 方法的时候

只能允许一个线程可以进行操作,这样操作的 count++ 就是排队进行的,结果一定是正确的

Q: 怎么实现排队效果?

A: Java 中 synchronized 关键字和 ReentrantLock 都可以实现对资源加锁,保证并发正确性。

2.2 示例2 synchronized 保证线程安全

代码

public class CAS02 {
    // 总访问量
    static int count = 0;

    // 模拟访问的方法
    // synchronized 修饰static request()方法,修饰的是类对象,锁的是类
    public static synchronized void request() throws InterruptedException {
        // 模拟耗时5毫秒
        TimeUnit.MILLISECONDS.sleep(5);
        count++;
    }


    public static void main(String[] args) throws InterruptedException {
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        // 开始时间
        Long startTime = System.currentTimeMillis();
        // 模拟100个用户
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                // 每个线程就是一个用户
                // 模拟每个用户发起十次访问请求

                try {
                    for (int j = 0; j < 10; j++) {
                        request();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    // 每个线程结束之后 threadSize -1
                    countDownLatch.countDown();
                }

            });
            thread.start();
        }
        // 保证100个线程结束之后,再执行后面的代码
        countDownLatch.await(); // threadSize==0 执行后面的代码
        Long endTime = System.currentTimeMillis();
        System.out.println("Time: " + (endTime - startTime) + ", count: " + count);
    }
}

结果

image-20210329210118319

分析

发现用 synchronized 关键字虽然完成了线程同步,成功输出 1000,但是发现十分耗时,说明 synchronized 关键字十分浪费资源,很大程度上锁住了一些没有不要锁的地方。

Q: 耗时太长的原因是什么?

A: 程序中 的request方法使用 ``synchronized 关键字修饰,保证了并发情况下的线程安全问题。但是这样子的加锁使得相当于串行执行了。

Q: 如何解决耗时长的问题?

A: count++ 操作实际上是由3步来完成

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

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

3.将B的值赋值给 count

升级第三步的实现

  1. 获取锁
  2. 获取以下 count 最新的值,记做 LV
  3. 判断LV 是否等于 A ,如果相等,则将 B的值赋值给 count ,并返回 true 否则 返回 false
  4. 释放锁

这样就会使得锁的范围就是第三步,所系了范围。

2.3 示例3 模拟CAS 保证线程安全

代码

public class CAS03 {
    // 总访问量
    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 void main(String[] args) throws InterruptedException {
        int threadSize = 100;
        CountDownLatch countDownLatch = new CountDownLatch(threadSize);
        // 开始时间
        Long startTime = System.currentTimeMillis();
        // 模拟100个用户
        for (int i = 0; i < 100; i++) {
            Thread thread = new Thread(() -> {
                // 每个线程就是一个用户
                // 模拟每个用户发起十次访问请求

                try {
                    for (int j = 0; j < 10; j++) {
                        request();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 每个线程结束之后 threadSize -1
                    countDownLatch.countDown();
                }

            });
            thread.start();
        }
        // 保证100个线程结束之后,再执行后面的代码
        countDownLatch.await(); // threadSize==0 执行后面的代码
        Long endTime = System.currentTimeMillis();
        System.out.println("Time: " + (endTime - startTime) + ", count: " + count);
    }


    /**
     * @param expectCount 期望值 count
     * @param newCount    需要给 count 赋值的新值
     * @return 成功返回 true 失败返回 false
     */
    public static synchronized boolean compareAndSwap(int expectCount, int newCount) {
        if (getCount() == expectCount) {
            count = newCount;
            return true;
        }
        return false;
    }

    private static int getCount() {
        return count;
    }
}

结果

image-20210329211048014

发现耗时时间变短了,而且又保证了线程安全

java中也有cas的应用

java从jdk1.5就将cas引入并且使用了,java中的Atomic系列就是使用cas实现的

例如,AtomicInteger(package java.util.concurrent.atomic;),有兴趣的了解的 点击我

3 CAS 能做什么?

可以解决多线程并发安全的问题,以前我们对一些多线程操作的代码都是使用synchronize关键字,来保证线程安全的问题;

现在我们将cas放入到多线程环境里我们看一下它是怎么解决的

我们假设有A、B两个线程同时执行一个int值value自增的代码,并且同时获取了当前的value,我们还要假设线程B比A快了那么0.00000001s,所以B先执行,线程B执行了cas操作之后,发现当前值和预期值相符,就执行了自增操作,此时这个value = value + 1;

然后A开始执行,A也执行了cas操作,但是此时value的值和它当时取到的值已经不一样了,所以此次操作失败,重新取值然后比较成功,然后将value值更新,这样两个线程进入,value值自增了两次,符合我们的预期。

4 CAS的缺点

1.CPU开销较大,长时间自旋非常消耗资源

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,循环往复,会给CPU带来很大的压力。

2.不能保证代码块的原子性

CAS机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用Synchronized了。。

<think>好的,用户之前让我介绍并发、JVM和框架相关的高频Java题目,现在又询问Markdown/PDF文档和Anki记忆卡的具体情况。我需要先理解用户的需求。 用户可能是在准备面试或者系统学习Java高级知识,需要这些资料来辅助复习。他们可能希望得到更结构化的学习材料,方便在不同场景下使用。Markdown/PDF适合系统学习,而Anki记忆卡适合利用碎片时间复习。 接下来,我要分析用户提到的三种资料形式的特点。Markdown和PDF适合详细阅读,便于打印和做笔记。Anki记忆卡则是利用间隔重复算法帮助记忆,这对记忆概念和关键点特别有效。用户可能希望知道哪种形式更适合他们的学习方式,或者想了解如何获取这些资料。 同时,用户可能没有明确说出的是,他们需要高效的学习工具,尤其是时间紧张的情况下。Anki的记忆卡可以帮助他们高效记忆,而详细的文档则用于深入理解。此外,用户可能还关心这些资料的内容是否全面,是否包含足够的实战案例。 我需要确保回答中涵盖每种格式的优点、适用场景,以及如何结合使用。还要提到可能的附加内容,比如实战题目,这能解决用户潜在的实际应用需求。最后,应该询问用户的具体需求,以便提供更针对性的帮助,比如是否需要更多代码示例或调优案例。 需要注意的是,用户可能对如何制作Anki卡片或如何组织Markdown文档不太熟悉,所以解释清楚这些工具的使用方法也是有必要的。同时,保持回答简洁,分点说明,方便用户快速获取信息。</think>以下是关于 **Markdown/PDF文档** 和 **Anki记忆卡** 的详细介绍及适用场景分析: --- ### **1. Markdown/PDF文档** #### **特点** - **结构化知识**:内容按知识点分类(如并发/JVM/框架),层级清晰,适合系统化学习。 - **代码与注释**:可直接嵌入代码片段、配置示例和调优参数(如GC日志参数、线程池配置)。 - **便携性**:PDF支持离线阅读、打印标注,Markdown易于用Git管理版本和协作。 #### **适用场景** - **深度学习**:需要反复查阅底层原理(如JVM内存模型、Spring循环依赖源码)。 - **面试复习**:快速定位高频考点(如CAS缺陷、G1回收流程)。 - **团队分享**:作为技术文档库,统一团队知识体系。 #### **示例内容片段** ```markdown ## **G1垃圾回收器核心参数** - `-XX:+UseG1GC`:启用G1 - `-XX:MaxGCPauseMillis=200`:目标最大停顿时间 - `-XX:InitiatingHeapOccupancyPercent=45`:触发并发GC的堆占用率阈值 ``` --- ### **2. Anki记忆卡** #### **特点** - **主动回忆**:基于“间隔重复算法”,自动规划复习频率,强化记忆薄弱点。 - **碎片化学习**:支持手机/PC端同步,通勤或排队时刷题。 - **自定义内容**:可添加题目类型(如选择题、填空题)、关联图片或代码截图。 #### **适用场景** - **概念速记**:快速记忆高频术语(如偏向锁、TLAB、Spring Bean生命周期)。 - **面试冲刺**:每天刷30张卡,巩固易忘知识点(如`volatile`禁止指令重排序原理)。 - **错题本功能**:自动记录错误次数,针对性复习。 #### **示例卡片内容** - **正面**:`synchronized锁升级过程?` - **背面**: ```text 无锁 → 偏向锁(单线程) → 轻量级锁(CAS自旋) → 重量级锁(OS互斥量) ``` --- ### **3. 如何结合使用?** | **场景** | **推荐工具** | **优势** | |------------------|-----------------------|--------------------------------------| | 通勤碎片时间 | Anki记忆卡 | 手机随时刷题,强化记忆点 | | 系统化学习/实战 | Markdown/PDF文档 | 结合代码示例和原理图深入理解 | | 模拟面试/笔试 | PDF文档+Anki | 快速检索答案,查漏补缺 | --- ### **附加内容(可选)** 1. **实战场景附加题**(如设计秒杀系统) - 提供架构图、代码代码、压测参数(如JMeter线程组配置)。 2. **调优案例库** - 真实线上问题分析(如Full GC频繁、Redis缓存穿透)。 --- ### **如何获取?** 1. **定制化需求**: - 是否需要增加 **“高频考点标注”**(如大厂真题)? - 是否需要 **“代码实操项目”**(如手写线程池、简化版Spring)? 2. **格式优化**: - Markdown支持代码高亮(如Java/Python),PDF可提供书签导航。 如果需要示例或模板,可告知具体方向(如并发/JVM优先),我会进一步提供!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值