前言
看了好多网上CAS的讲解,但是都是简单介绍,真正亮点都没有讲到,其实想要用CAS征服面试官很简单,请跟我来呗 Tip:同道中人:能看到这,证明你其实是一个有追求的程序猿,点赞,收藏转发一波呗,感激不尽
一、CAS是什么
CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
二、CAS操作
CAS有三个操作数:当前值A、内存值V、要修改的新值B、 1.假设 当前值A跟内存值V相等,那就将内存值V改成B 2.假设 当前值A跟内存值V不相等,那么就重试否则放弃更新 其实将当前值与内存值进行对比,判断是否有被修改过,这就是CAS的核心
三、CAS案例
代码如下(示例):
// 统计计数在单线程是没有问题的,多线程是有问题的
public class Counter {
private int count;
public Counter(){}
public int getCount(){
return count;
}
public void increase(){
count++;
}
}
//通过synchronized改良后的 多线程统计计数,但是效率是不行的【悲观】
public class Counter {
private int count;
public Counter(){}
public synchronized int getCount(){
return count;
}
public synchronized void increase(){
count++;
}
}
//通过CAS机制,乐观锁public class Counter {
//其实CAS底层都是使用的UnSafe这个工具类中的方法
private AtomicInteger count = new AtomicInteger();
public Counter(){}
public int getCount(){
return count.get();
}
public void increase(){
count.getAndIncrement();
}
}
该处主要讲的就是多线程下的 synchronized 与 cas的对比,synchronized锁每次只会让一个线程去操作共享资源,而CAS其实就是乐观锁,没有加锁,多个线程都可以直接操作共享资源,在实际去修改的时候去判断是否成功,没有成功则自旋。在多数情况下会比synchronized锁要高效些,比如对一个值的累积,使用juc下的Atomic类就足够了
三、CAS缺点之ABA
CAS的缺点之一就是带来ABA问题,比如说一个线程A读到当前值是20,线程B又修改了30,然后线程C又将值修改回10,对于线程A来说这个值是没变的,线程A是可以执行修改的。但是其实是不合理的,因为这个值已经经过B,C的修改,(比如CAS监听的是一个对象,对象多个值)这就是所谓的ABA问题 AtomicStampedReference类用来解决ABA问题,加入版本号,修改值的同时对 字段Version进行加1操作,那么ABA的问题就解决了,说白了就是同时两个值都没变化的情况下去修改,默认线程只能操作目标对象值,而version字段累加操作不受控制。默认自增
四、CAS缺点之自旋
CAS一只让大家诟病的就是自旋,因为它为了无锁化,所以一只存在自旋操作:就是一旦没修改成功,则继续重新访问目标值,然后修改,基于底层原子操作进行自旋。JDK1.8之后推出了LongAdder对象,比Atomic相关原子类处理更好。它解决了自旋问题。简单理解AtomicLong在累加的时候是对一个值做运算,但是在高并发的情况时,只有一个线程是执行成功的,其他的都会失败,不断自旋(重试),最终自旋会成为瓶颈的。而LongAdder的核心思路就是 要操作的目标 分散到数据Cell中
五、LongAdder
简单介绍下LongAdder:成员变量cellsBusy cellsBusy作用是当要修改cells数组时加锁, 防止多线程同时修改cells数组, 0为无锁,1为加锁,加锁的状况有三种 1 cells数组初始化的时候 2 cells数组扩容的时候 3 如果cells数组中某个元素为null,给这个位置创建新的Cell对象的时候 成员变量base 它有两个作用:1 在开始没有竞争的情况下,将累加值累加到base 2 在cells初始化的过程中,cells不可用,这时会尝试将值累加到base上 成员变量cells cells数组是LongAdder高性能实现的必杀器:AtomicInteger只有一个value,所有线程累加都要通过cas竞争value这一个变量,高并发下线程竞争非常严重;而LongAdder则有两个值用于累加,一个是base,它的作用类似于AtomicInteger里面的value,在没有竞争的情况不会用到cells数组,这时使用base做累加,有了竞争后cells数组就上场了,第一次初始化长度为2,以后每次扩容都是变为原来的两倍,直到cells数组的长度大于等于当前服务器cpu的数量为止就不在扩容(CPU能够并行的CAS操作的最大数量是它的核心数),每个线程会通过线程对cells[threadLocalRandomProbe%cells.length]位置的Cell对象中的value做累加,这样相当于将线程绑定到了cells中的某个cell对象上。
写在最后,感谢点赞关注收藏转发
欢迎关注我的微信公众号 【猿之村】
来聊聊Java面试 加我的微信进一步交流和学习,微信手动搜索 【codeyuanzhicunup】添加即可 如有相关技术问题欢迎留言探讨,公众号主要用于技术分享,包括常见面试题剖析、以及源码解读、微服务框架、技术热点等。