欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java并发编程之CAS

程序员文章站 2022-03-08 20:11:45
...

在进行Java并发编程的时候,我们都会使用到锁来控制并发线程对临界资源的安全访问。用的最多的就是synchronized,但是synchronized属于重量级锁、悲观锁,在很多时候会引起性能问题。除了synchronized还有cas,cas属于乐观锁,在一定的场景下会比synchronized更高效。java.util.concurrent包下的ReentrantLock内部的AQS,还有各种Atomic开头的原子类,内部都应用到了CAS。

AtomicInteger源码分析

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

此方法是在当前值的基础上加一, 继续深入源码

public final int getAndAddInt(Object var1, long var2, int var4) {
       	int var5;
        do {
            var5 = this.getIntVolatile(var1, var2); //1
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //2

        return var5;
    }

此方法就是CAS的执行流程,首先获取内存中的值(1),在执行加一之前将上一步获取到的值和现在内存中的进行比较,如果相等,说明没有其他线程修改这个值,可以更新,否则如果不相等,则说明有其他线程修改了这个值,则不能更新,继续步骤1

CAS执行流程

Java并发编程之CAS

CAS的优点

synchronized属于重量级锁,当线程无法获取临界资源时,此线程会阻塞,等到锁被释放再此唤醒该线程,这个过程涉及到线程的上下文切换和状态改变,如果实际执行的内容较为简单那么就会出现上下文切换和状态改变的时间大于用户代码执行时间,浪费资源,降低程序性能。这时候我们倾向于使用CAS,当无法获取锁的时候,不放弃cpu的执行时间,而是通过自旋不断地获取锁,这就是自旋锁。

CAS存在的问题

ABA问题

概述

CAS会在更新数据之前检查值是否发生改变,如果没有变化则更新,但如果一个值出现这样的变化A-B-A,那么CAS在更改的时候就会发现值没有变化,但是实际上却发生了改变。

场景

取款:本来有100元,提取50元,由于某些原因有两个线程取款
线程1:获取当前值100,期望更新为50
线程2:获取当前值100,期望更新为50
线程1成功执行,线程2某种原因block了,这时,某人给此账户汇款50
线程3:获取当前值50,期望更新为100,这时候线程3成功执行,余额变为100
线程2从Block中恢复,获取到的也是100,compare之后,继续更新余额为50
此时可以看到,实际余额应该为100(100-50+50),但是实际上变为了50(100-50+50-50)

解决方法

给变量加上版本号:1A-2B-3A.比较时不止比较值相等,版本号也要一样

自旋开销可能很大

问题描述

如果CAS自旋一直获取不到锁,那么就会给cpu带来很大的执行开销

解决办法

使用适应性自旋锁,对自旋等待的时间加上限度,如果自旋超过了限定的次数,就挂起当前线程

相关标签: java