一、CAS简介
CAS是原子类的底层原理,同时也是乐观锁的原理,CAS全称Compare-And-Swap,中文含义“比较并交换”,它是一种思想,一种算法。
特点,避免使用互斥锁,
当多个线程同时使用CAS更新同一个变量时,
在多线程的情况下,为了保证并发的安全性,我们可以使用互斥锁,而CAS的特点是避免使用互斥锁,当多个线程同时使用CAS更新同一个变量时,只有其中一个线程能够操作成功,而其他线程都会更新失败。不过和同步互斥锁不同的是,CAS不会让失败的线程阻塞,而是被告知这次由于竞争而导致的操作失败,但还可以再次尝试。
CAS广泛用在并发编程领域中,以实现那些不会被打断的数据交换操作,从而实现无锁的安全。
CAS思路
CAS核心思路是,当前的内存值V,预期值A,要修改的值B,仅当当前的内存值V和A值相同时,才将内存值修改为B。
CAS会提前假定当前内存值V应该等于值A。而值A往往是之前读取到当时的内存值V,在执行CAS时,如果发现当前的内存值V恰好是值A的话,CAS就会把内存值V改变成值B。而值B往往是在拿到值A后,在值A的基础上经过计算而得到的。
如果执行CAS时发现此时内存值V不等于值A,则说明在刚才计算B的期间内,内存值已经被其他线程改过了,那么本次CAS就不应该再修改了。可以避免多人同时修改导致出错。
CAS实现的是无锁算法是写上机制,
互斥锁不存在写上机制,大家都会尝试抢占资源,利用CAS和利用互斥锁都已保证并发安全。是实现相同目标的不同手段。
例如:
CPU1 内存值V是100, 假设有两个线程,分别使用CPU,他们都是利用CAS来改变右边的变量的值。我们先看线程1,它使用CPU1,假设它先执行,它期望当前的值是100,并且想将100,修改为150,在执行的时候,它会去检查当前的值是不是100,发现真的是100,所以可以改动成功,而当改完之后,右边的值就会变为150。
CPU2,内存值V是150,线程2,它使用CPU2,假设它来执行,它想把这个值从100,改为200,但是发现预期值和内存地址不一致,所以修改失败。
CAS等价语义的代码
/**
* 描述: 模拟CAS操作,等价代码
*/
public class SimulatedCAS {
private int value;
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
//这一行就说明 期望的A值等于 内存值V,就改把V该成B值。
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
}
两个线程竞争CAS的,一个线程改变了值,另一个线程就不会再改变值了。
二、CAS的和乐观锁的关系,什么时候会用到CAS?
在并发容器中的应用
ConcurrentHashMap中使用了
ConcurrentLinkedQueue
在数据库中的应用
数据库中也存在对乐观锁和CAS思想的应用
更新数据时,可以利用version字段在数据库中实现乐观锁和CAS操作,而在获取和修改数据时都不需要加悲观锁
在原子类中的应用
在原子类中,例如AtomicInteger,也是使用了CAS,AtomicInteger的getAndAdd方法。
三、CAS的缺点
优点是避免使用互斥锁,使性能更高。
1、但是它的缺点是ABA问题,A变成B,又变成A。CAS检查的是并不是值没有发生变化,而是去比较这当前的值和预期值是不是相等。
在变量值自身之外,再添加一个版本号,这个值的变化路径就从A->B->A变成了1A->2B-3A。通过这样的思路就可以解决ABA的问题。
2、自旋的时间过长,如果应用场景本身就是高并发的场景,就有可能导致CAS一直都操作不成功,循环时间就会越来越长,根据实际情况是否使用CAS,在高并发的场景下,通常CAS的效率是不高的。
3、不能灵活的控制范围。
通常我们执行CAS时,是针对某一个,而不是多个共享变量,这个变量可能是Integer类型,也有可能是Long类型、对象类型。
如果想对多个对象同时进行CAS操作,是比较困难的。解决方法就是,利用一个新的类,整合刚才这一组共享变量。