JAVA线程-CAS原理

目录

1.CAS介绍

2.应用场景

3.使用 synchronized

3.1 分析 synchronized

4 使用 AtomicInteger

4.1 CAS机制

4.2 与 synchronized 比较

4.3 CAS缺点

4.4 CAS改进

5 总结


1.CAS介绍

CAS全称为CompareAndSwap,直译为“比较并替换”。其实CAS的应用场景非常多,但实际在开发很难感知到,更多的做为一种思想或底层实现封装起来。常见的如:乐观锁、volatile、AtomicInteger、AtomicBoolean...、JUC包的并发实现。

2.应用场景

假设需要对一个data变量做 100 次“++”操作交由 2 个线程执行,结果会是什么。

public class CasTest {

    private static int num = 2;

    private static ExecutorService executor = Executors.newFixedThreadPool(num);

    private static int data = 0;

    public static void main(String[] args) throws InterruptedException {

        CasTest casTest = new CasTest();
        for (int i = 0; i < 100; i++) {
            executor.execute(casTest::increment);
        }

        // 休眠1秒等待线程执行
        Thread.sleep(1000);

        System.out.println("data:" + data);

        executor.shutdown();
    }

    private void increment() {
        data++;
    }

}

结果的范围为2~100,为什么可以看看这篇文章的分析 。很明显与期望的100有很大出入。因为并发的对data变量修改,在读取、操作、替换这三个步骤发生了线程不安全的问题。因此出现结果不准确。

3.使用 synchronized

public class CasTest {

    private static int num = 2;

    private static ExecutorService executor = Executors.newFixedThreadPool(num);

    private static int data = 0;

    public static void main(String[] args) throws InterruptedException {

        CasTest casTest = new CasTest();
        for (int i = 0; i < 100; i++) {
            executor.execute(casTest::increment);
        }

        // 休眠1秒等待线程执行
        Thread.sleep(1000);

        System.out.println("data:" + data);

        executor.shutdown();
    }

    private synchronized void increment() {
        data++;
    }

}

3.1 分析 synchronized

通过上述修改,可以发现data的数值在多线程运行下始终为100。线程安全。但synchronized到底做了什么?程序在编译后都会转为指令,而synchronized在方法体指令执行的前后分别加上 monitorenter monitorexit 这两个字节码指令。

在执行 monitorenter 指令时,首先要尝试获取对象锁。如果这个对象没被锁定或者当前线程已经占有锁,则锁的计数器加 1 ;再执行 monitorexit 指令时会将锁计数器减 1 ,当计数器为0时。锁就被释放。如果获取锁失败,则线程阻塞。直至对象锁被释放。不难想象使用了 synchronized 是的线程调用 increment 方法都变为串行化执行。

设想 increment 方法中还需要做的很多,但这些操作并不涉及线程安全问题。synchronized迫使整个方法串行化,可想而知操作效率并不高。

4 使用 AtomicInteger

public class CasTest {

    private static int num = 2;

    private static ExecutorService executor = Executors.newFixedThreadPool(num);

    private static AtomicInteger data = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {

        CasTest casTest = new CasTest();
        for (int i = 0; i < 100; i++) {
            executor.execute(casTest::increment);
        }

        // 休眠1秒等待线程执行
        Thread.sleep(2000);

        System.out.println("data:" + data);

        executor.shutdown();
    }

    private void increment() {
        // ....
        data.incrementAndGet();
        // ....
    }

}

代码中之所以不去掉 increment 方法是想表达,方法内可能还有额外的操作。但就当前这个场景。完全可以讲同步把控的范围缩小为data变量自身。这里使用的 AtomicInteger 是jdk并发包中的Atomic原子类。应对多线程并发修改。而 Atomic 线程安全主要依赖于的底层就是使用的 CAS 机制。

4.1 CAS机制

场景:假设三个线程同时对 data 做 incrementAndGet 操作,CAS是如何解决冲突问题?

过程梳理

(1)线程1 获取data值为0。

(2)线程1 执行CAS。先比较data是否等于0,是所以放心的修改为1。

(3)线程2、线程3 同时获取data值为1。

(4)线程2 率先执行完加 1操作。执行CAS。此时data等于修改前获取的值,所以放心修改为2。

(5)线程3 执行完加 1操作。执行CAS。此时发现data已经不是修改前获取的值。CAS失败。

(6)线程3 重新执行业务。再次获取data值为2。

(7)线程3 执行完加 1操作。执行CAS。此时data等于修改前获取的值,所以放心修改为3。

(8)至此业务结束,data在多线程操作下安全的修改为 3。

4.2 与 synchronized 比较

本质上各有各的应用场景,synchronized也做了很多优化如 偏向锁、轻量级锁、重量级锁。性能有了很大提升。但就多线程修改data这个业务场景。针对的是变量而非方法。故并不需要锁机制,将一些无线程安全问题的操作都一并纳入同步代码中。而CAS更切合这个场景下变量同步的使用。

4.3 CAS缺点

(1)ABA问题

CAS在修改时需要确认修改值是否有改动。但有一种情况是值原来是A。

步骤一:线程1 获取后修改为B但尚未执行CAS。

步骤二:此时线程2 修改为B,并完成CAS。

步骤三:线程3 再次修改B为A,并完成CAS。

步骤是:回到线程1 在执行CAS 发现值还是A,就赋值为B。

但实质上A已经不是初始条件的A。在某些情景似乎不影响业务,例如上述的data++。但如果使用data记录某种原子操作。此时就有问题。因为ABA导致操作非原子。

(2)多线程自旋

如果多线程执行CAS由于业务耗时原因或者其他原因,导致CAS频繁失败。此时系统处理任务的效率会非常低。CPU资源消耗严重。都用在了不断重试。

(3)无法同时保证多个共享变量的原子性

当对一个共享变量执行操作时,可以使用CAS保证原子操作。但涉及多个共享变量操作时,CAS就无法保证操作的原子性。此时可以使用锁机制。也可以使用AtomicRefrence保证应用对象之间的原子性。设置一个对象属性为共享变量。

4.4 CAS改进

针对CAS缺点中的“多线程自旋”,设想下如果一个线程维护一个变量是不是就没有这种问题。但这就不是共享变量了。另一种想法就是是否可以将共享变量做拆分。类似 ConcurrentHashMap “分段锁” 原理。修改时只要确保线程分到的段未被修改。减少 “自旋”的可能性,从而提升性能。

在JDK8中就有 LngAdder DoubleAdder 其原理就是 “分段CAS” 和 “自动分段迁移”。

分段CAS: 内部会维护一个Cell数组,数值的每一个索引为一个分段记录则值一部分。

自动分段迁移: Cell的值在执行CAS失败后会自动切换到另一个Cell分段执行CAS操作。

5 总结

CAS在某种情景固然是提高了并发操作的性能。减少了上锁的问题。但CAS也有其自身的缺点。任何技术都不是“一劳永逸”的。具体问题还要具体分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值