CAS算法
建议:java开发程序员 使用 java 5 之后提供的 java.util.concurrent.acomic包中的cas类 使用java5+提供的cas特性,而不是自己实现,好处是使得开发更加的便捷,并且可以利用机器cpu的cas特性,是的cas的代码运行的更快。
1. 什么是cas机制
在仔细理解CAS之前 我们先了解以下什么是java的锁,多线程编程会因为多个线程之间数据同步问题导致实际结果与预期结果大相径庭的问题,之前都是使用的synchronized关键字来保证同步,这是一种独占锁 ,也就是悲观锁。
当然也可以使用volatile设置线程的共享变量,volatile只是保证了共享变量的可见性,并禁止指令重排序,保证了程序的的有序性,即共享变量被修改之后可以将修改后的值立即写入主存,因此其他线程去主存拿到的就是最新的值,但是volatile 不保证操作的原子性。
操作的原子性还是在synchronized和lock来保证的。
但是锁机制是有一些问题的:
- 多线程竞争下,加锁、释放锁的操作一方面会造成而外的上下文切换和时间调度,影响效率和性能。
- 一个线程持有锁,会导致其他需要此锁的线程挂起。
2. CAS
CAS:(compare and swap)比较和替换 是 设计 并发算法时用到的一种技术。是一种无锁算法。
cas操作包含三个操作数:1. 原数值(内存中的数值V)、2. 预期原值(旧的预期值 A)、3. 新值(要修改的新值 B)。如果V 和 A相同 ,则将V的值更新为B中的值,否则的话,cas处理器不会做任何操作。
举例:线程a 和 线程b 都去修改一个值 c (V=50),此时他们分别会将c的值拷贝到自己的内存中,此时a/b内存中的预期值A都等于50,假如,此次竞争修改中,a成功了 ,a将c改为了51,(ps:因为没有锁,竞争失败的线程不会挂起,而是被告知竞争失败,可以再次尝试去修改),此时b再去修改c,发现b中的预期原值A不等于 V,此时操作就失败了,(ps:与之前竞争失败不同),b将重新读取c的值,更新自己的A为51之后再去尝试修改c。
CAS基于共享数据不会被修改的假设,当同步冲突很少的时候,效率能够有很大的提升,乐观锁就是基于CAS来做的。
那么CAS是不是就没有问题了呢?
不是的,CAS的问题:
- ABA问题:线程a:51-》49,线程b将 49-》51,那么对于线程c来说 51 这个值没有变化过,但是实际变化了。**解决思路是:**引入版本号。a:51-》a49 b: a49-》c51 . java5开始atomic包里提供了一个类AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
- **循环时间开销大:**如果一个线程一直修改不成功,那么这个线程调用CAS不停的自旋,会给cpu带来很大的执行开销。
- 只能保证一个共享变量的原子操作:对于多个共享变量操作时,CAS只能保证对于单独的一个变量他的操作时原子性的,但是对于变量总体来说则不一定,例如:有三个变量a、b、c和两个线程A、B,CAS保证对于a或者b或者c来说,在操作他们的时候是一个线程单独操作的,但是CAS不能保证,线程A在操作a的时候 ,b的值也是线程A来操作的。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
题外了解:concurrent包的实现
由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:
- A线程写volatile变量,随后B线程读这个volatile变量。
- A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。
- A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:
- 首先,声明共享变量为volatile;
- 然后,使用CAS的原子条件更新来实现线程之间的同步;
- 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。
AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d0gWT92P-1621576374899)(img\b7b2472f-6b93-3f85-9e44-29a9ff774c8e.png)]