介绍
- synchronized关键字与Lock等锁机制都是悲观锁(悲观的想法:我在操作的时候,自认为一定会有其他线程来和我争抢,所以在操作前一定会先上锁),确保了接下来的所有操作都由当前线程去执行的。
- 乐观锁(乐观的想法,我在操作的时候,自认为不会有其他的线程与我争抢锁):线程在操作之前不会做任何预先的处理,而是直接去执行。当在最后执行变量更新的时候,当前线程需要有一种机制来确保当前被操作的变量是没有被其他线程修改的;CAS是乐观锁的一种极为重要的实现方式。
- CAS(Compare And Swap) 比较与交换:这是一个不断循环的过程(CAS取到最新的变量值与自己保存的变量值比较,发现值不对应,则重新获取变量值,直到变量值一致。这就表示该变量没有被其他线程修改,CAS可以继续向下操作),一直到变量值被修改成功。CAS本身是由硬件指令提供支持的,硬件中是通过一个原子执行来实现比较与交换的。因此CAS可以确保变量操作的原子性(成功 或 失败,没有其他中间结果)。
底层实现
对于CAS来说,其操作数 主要涉及如下三个:
- 需要被操作的内存值V
- 需要进行比较的值A
- 需要进行写入的值B
只有当V == A的时候,CAS才会通过原子操作的手段来将V的值更新为B.
最终是通过汇编语言实现的:
lock cmpxchg # 指令
注: cmpxchg = cas修改变量值。cmpxchg是非原子性操作,假设线程A 、线程B,对值进行CAS比较后,都通过了CAS检查。那么假设线程B先写入了,此时线程A在写入那么就会覆盖线程B的修改。所以在汇编语言中加了lock,表示一次只允许一个CPU进行修改。
限制:
1、循环开销问题:并发量大的情况下会导致线程一直自旋。
2、只能保证一个变量的原子操作:可通过AtomicReferenc来实现对多个变量(一个对象)的原子操作。
3、ABA问题:变量初始值为1,线程Q执行CAS更新时,正巧线程W将初始值改为了3,紧接着又把3改回了1,线程W退出。所以出现了变量值突变的一种情况 1–>3–>1。
这时线程Q一开始看见的值为1,现在值确实是1,所以CAS可以正常执行。
结果正确,但逻辑不严谨。CAS要处理,只能在增加一个变量,每次比较 值、值的版本号(修改次数)。Jdk提供了AtomicStampedReference这个类,通过维护一个对象引用以及一个整数“stamp”,解决ABA问题。
注:资料主要来源【张龙】