CAS和Atomic
在了解CAS和Atomic之前我们要先来认识一下悲观锁和乐观锁的一个概念!
悲观锁和乐观锁
悲观锁
假设是每次获取数据都认为会被其他线程修改,每次来操作数据(可读、可写)的时候都会加锁
悲观锁的实现是Synchronized
悲观锁存在问题:
1、多线程竞争,引起性能问题 (锁的释放和获取需要上下文切换和调度等)
2、一个线程获取锁会导致其他线程被挂起(则塞)
乐观锁
假设对数据的操作一般都不会发生冲突,读取操作时,不会加锁,在对数据进行变更操作是,才来检测当前的数据是否发送冲突,发生冲突返回错误信息,让用户类决定如何做
乐观锁阐述的思路,主要有两个步骤操作:冲突检测和数据更新,该方式的实现就是CAS是乐观锁
乐观锁、悲观锁是一种思想,CAS是乐观锁这种思想的实现方法
CAS
CAS的英文为Compare and Swap 翻译为比较并交换多个线程通过CAS尝试修改同一个变量,只有一个线程在同一时刻进行修改,而其他的操作失败,失败的线程不会挂起,告诉失败的线程可以再次尝试:
CAS操作涉及到三个操作数:
需要读写的内存位置(V)、进行比较的预期的原值(A)、待写入的新值(B)
如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“ 我认为位置 V 应该包含值A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。
CAS引起ABA的问题
假设存在以下线程操作序列:
1、线程1从内存位置V中取得A
2、线程2从内存位置V获取A
3、线程2进行一些操作,将A修改为其他的结果,将B修改为A
4、线程2将A再次写入到位置V
5、线程1进行CAS操作,发现位置V中任然是A,直接修改为B,操作成功
6、尽管线程1操作成功,但并不该表该过程没有问题,对于线程1而言,线程2 的修改以导致数据丢失
举例说明:一个链表ABA的例子:
1、现有一个单向链表实现的堆栈,栈顶为A,线程1获取到A.next为B,线程1希望通过CAS操作将栈顶替换为B
2、在线程1执行CAS操作之前,线程2来执行,将A、B出站,在依次入栈D、C、A,而对象B处于游离状态
3、此时线程1执行CAS操作,检测栈顶为A,CAS成功执行,栈顶为B,实际是B.next = null,此时堆栈只有一个B,C和D组成的链表不在堆栈中,C\D 被丢弃了
ABA问题的解决方案
ABA问题的解决需要使用版本号,在变量前加上版本号,每次变量的变更操作版本号+1,那么A-B-A就变成1A-2B-3A
使用CAS会引发的问题
CAS虽然比Java中提供的锁的开销小,但是存在问题
1、ABA问题
ABA问题通过版本号解决
2、循环时间长开销大
自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3、只能保证一个共享变量的原子操作
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁,或者有一个取巧的办法,就是把多个共享变量合并成一个共享变量来操作。比如有两个共享变量i=2,j=a,合并一下ij=2a,然后用CAS来操作ij。从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作。
Atomic
Java 中Atomic操作,比如AtomicInteger等原子操作,提供了i++等原子操作
如果读取操作,等将value声明为volatile的,保证在没锁的情况下,数据是同步的
private volatile int value;
public final int get() {
return value;
}
涉及到数据变更:AtomicInteger的++i操作:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而这里的comparAndSet(current,next)就是前面介绍CAS的时候所说的依赖JNI实现的乐观锁做法:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
除了基本数据类型的原子操作类,JUC还提供了一下的Atomic类