什么是CAS
CAS(Compare And Swap 缩写),中文翻译比较并交换,实现并发算法时常用的一种技术,主要是通过处理的指令来保证操作的原子性。它包含三个操作数:
1. 内存位置:V表示
2. 预期原值:A表示
3. 更新值:B表示
执行CAS操作的时候,将内存位置的值与预期原值进行比较:
- 如果相匹配,那么处理器会自动将该位置值更新为新值
- 如果不匹配,处理器不做任何操作或者重试(这种重试的操作称为自旋),多个线程同时执行一个CAS只会有一个成功
Unsafe
Unsafe是CAS的核心类,由于Java无法直接访问底层操作系统,需要通过本地native方法来访问。Unsafe类就相当于一个后门,基于该类可以直接操作特定的内存数据,Unsafe类存在sun.misc包中,其内部方法操作可以像C的指针一样操作内存,因此Java中CAS的操作方法都是依赖于Unsafe类的方法。
自旋锁(spinlock)
CAS是实现自旋锁的基础,CAS利用CPU指令保证了操作的原子性,以达到锁的效果。
自旋:是指尝试获取锁的线程不会立刻阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取成功。这样的好处就是减少了线程的上下文切换,缺点是循环会消耗CPU。
CAS缺点
1.ABA问题
ABA问题就是指CAS在更新的时候,读取到的值A,准备赋值的时候仍然是A,但是实际上有其他线程将A的值改为B,然后又改回了A。这个CAS更新的漏洞就是ABA问题,其实ABA问题对于常用的基本数据类型是不影响最终效果的。当用于复杂的数据结构就可能有问题。我举个例子,假设现在有个链表并且这个链表可以复用节点:
线程1和线程2同时操作链表:
1. 线程1:试图删除数据A,线程1的时间片用完切换到线程2
2. 线程2:线程2开始运行
把数据A移除
把数据B移除
把数据A加回到节点头部(节点A被复用了)
3.线程2时间片用完,切换到线程1,线程1发现内存指向的节点并没有变化所以继续执行compare And Swap 操作 成功
4.内存指向了一个错误的节点
其实解决上面的问题很简单,因为对象的引用是相同的,我们只需要对每个操作加入一个版本号或者随机数字就能判断操作的是不是被修改过的对象。
其实Java中有AtomicStampedReference这个类来解决这个问题。
2.循环时间长开销大
自旋CAS时间过长时间不成功,会给CPU带来很大开销