前言
CAS的应用场景
- 悲观锁:synchronized关键字与Lock等锁机制都是悲观锁。无论做何种操作,首先都需要先上锁,接下来再去执行后续操作,从而确保了接下来的所有操作都是由当前这个线程来执行的。
- 乐观锁:线程在操作之前不会做任何预先的处理,而是直接去执行;当在最后执行变量更新的时候,当前线程需要有一种机制来确保当前被操作的变量是没有被其他线程修改的;CAS是乐观锁的一种极为重要的实现方式。
CAS ( Compare And Swap )
- 比较与交换:这是一个不断循环的过程,一直到变量值被修改成功为止。CAS本身是由硬件指令来提供支持的,换句话说,硬件中是通过一个原子指令来实现比较与交换的;因此,CAS可以确保变量操作的原子性。
- 例如,有一个变a=1,此时,有一个线程读取到变量a=1,它会记住这个变量,当这个线程想要进行更新变量a=a+1操作时,先查看变量a是不是它以前记得的a=1,如果是就执行a=a+1;如果不是,就记住这个变量的最新值,不进行更新操作(a=a+1),在下一个循环进行更新前再进行比较,是不是前面记录的最新值,是就更新,不是就不能更新,直到更新成功为止。(在多线程情况下,变量有可能被其他线程更改,所有需要CAS)
- 记住,CAS是原子操作,由硬件实现。 不可能在能进行更新时的瞬间,变量被其他线程更改的情况。
先从一个样例说起
public class CAST {
private int count;
public int getCount() {
return count;
}
public void inCrease(){
++this.count;
}
}
复制代码
可以看到
++this.count;
一行代码有4个字节码指令,就不能保证它的原子性
读取->修改->写入 这三个操作并非原子操作
可以使用synchronized,加锁,来让其操作具有原子性,但这是悲观锁,有点耗性能
Java并发包中有针对对单个变量操作具有原子性的类—Atomic**
前面说过CAS在CPU是一调指令,但是Java代码却不是。Java为使用CPU的特性,比如CPU的缓存一致性协议,Java引入了volatile,来触发CPU缓存一致性,实现可见性,禁止指令重排等。
为了使用CPU的CAS指令Java也进行了类似的操作,及并发包中的atomic包
以AtomicInteger为例
public class AtomicTest {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);//初始值为5
System.out.println(atomicInteger.get());//返回值
System.out.println(atomicInteger.getAndSet(8));//返回旧的值5,设置新的值8
System.out.println(atomicInteger.get());//返回值
System.out.println(atomicInteger.getAndIncrement());//返回旧的值8,并自增1
System.out.println(atomicInteger.get());//返回值
/*
上面的方法都是原子操作
*/
}
}
复制代码
大致了解AtomicInteger的原理
对于CAS来说,其操作数主要涉及到如下三个:
1. 需要被操作的内存值V
2. 需要进行比较的值A
3. 需要进行写入的值B
只有当V==A的时候,CAS才会通过原子操作的手段来将V的值更新为B。
- 看看里面的
getAndSet()
方法
Atomic**
缺点
- 关于CAS的限制或是问题:
- 循环开销问题:并发量大的情况下会导致线程一直自旋(循环)
- 只能保证一个变量的原子操作,即不可以
a=b+1
,但是可以通过AtomicReference
类来实现对多个变量的原子操作 - ABA问题:1-> 3 ->1。即在更新前,值被另外一个线程从旧值(1)变成新值(3)再变成旧值(1),当前线程发现不了值在中途有变化过,在更新前进行比较发现值没变(还是1),遂进行更新。
- 虽然该问题对结果没什么影响,但在语义上是不正确的
- 可以通过给变量加(绑定)版本号(version),即变量更新一次,版本号+1。或者
- 通过时间戳(Timestamp),即变量更新时将更新的时间与其绑定,
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END