CAS:Compare And Swap,是一种无锁算法。在不使用锁即没有线程被阻塞的情况下实现多线程之间的变量同步。
1.CAS算法的核心算法原理
比较和交换是用于实现多线程同步的原子指令
。
是指将内存位置的内容与给定值进行比较。只有在相同的情况下,将该内存位置的内容修改为新的给定值。
涉及到三个操作数:
需要读写的内存值V;
进行比较的值A;
需要写入的新值B;
当且仅当比较值A与内存值V相同时,会将内存值修改为新值B并会返回true,否则什么也不做,并返回false。
这个过程是作为单个原子操作完成的。
比较和交换整体是一个原子操作。交换是一个不断重试的操作。
CAS是一种系统原语,原语的执行是连续的,在执行过程中不允许被中断
。
多个线程线程同时使用CAS操作一个变量时,只有一个会胜出并成功更新,其余均会失败。
失败的线程不会挂起,允许进行再次尝试,也允许失败的线程执行放弃操作不再重试
。
CAS是无锁操作,无锁操作中没有所锁的存在,不会出现死锁,即免疫死锁
。
- Java中执行CAS的实践者--
Unsafe类
存在于sun.misc包中。
该类内部的方法可以像C的指针一样直接操作内存。类中的所有方法都是native修饰的,都可以直接调用操作系统底层资源执行相应任务。
Unsafe类中存在直接操作内存的方法
。
无锁CAS操作基于Unsafe类中的以下方法实现:
//第一个参数o为给定对象,offset为对象在内存的偏移量,通过这个偏移量迅速定位字段并设置或获取该字段的值
//expected表示期望值,x表示要设置的值
//下面3个方法都通过CAS原子指令执行操作
public final native boolean compareAndSwapObject(Object o, long offset,Object expected, Object x);
public final native boolean compareAndSwapInt(Object o, long offset,int expected,int x);
public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);
补充
:Native方法
Native方法就是一个Java调用非Java代码的接口,该方法的实现由非Java语言实现。
定义native方法时,不提供实现体,实现体是由非Java语言在外面实现的。
native标识符暗示这个方法的实现体是非Java的,所有不可以和abstract标识符连用。
native方法对应的实现不是在当前文件,是在用其他语言实现的文件中。
调用Native方法
Java语言不能对操作系统底层进行访问和操作,可以通过JNI接口
调用其他语言来实现对底层的访问。
JNI:Java Native Interface。
JNI是Java本机编程接口,是Java的SDK的一部分,允许Java代码使用以其他语言编写的代码和代码库
。
- 并发包Atomic中使用CAS原理实现的并发类
JUC包下的atomic包中的原子类就是通过CAS来实现了乐观锁。
JUC的atomic包下,是对Java部分数据类型的原子封装,在原有数据类型的基础上,提供了原子性的操作方法,保证了线程安全
。
java中的CAS的实现
看一下原子类AtomicInteger的源码定义:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
//获取指针类Unsafe
private static final Unsafe unsafe = Unsafe.getUnsafe();
//AtomicInteger的value实例在内存中的偏移地址
private static final long valueOffset;
static {
try {
//通过Unsafe类中的方法得到value变量的内存偏移地址
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
}
unsafe
:获取并操作内存的数据。
Unsafe类是CAS的核心类。java中的CAS主要使用的是Unsafe类中的方法来进行操作特定内存的数据。
valueOffset
:该AtomicInteger类的value变量在内存中的偏移地址。
Unsafe类就是根据内存偏移地址获取数据的。
value
:存储的要进行原子操作的int值,使用volatile关键字修饰保证其在线程间是可见的。
AtomicInteger类在并发下的自增操作
//AtomicInteger类中的自增方法
/**
* Atomically increments by one the current value.
* 原子的增加一个当前值
* @return the updated value
*/
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
//Unsafe类中的方法
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//当前线程根据偏移地址拿到内存中的value值
var5 = this.getIntVolatile(var1, var2);
//在while循环中不断进行内存位置的内容与给定值的比较,相等就会进行更新,不等就会不断重试
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
当AtomicInteger类进行并发下的对自己的value变量的自增操作时,比如说A和B两个线程同时进行自增。
a. A与B会先通过getIntVolatile()方法
得到内存中的存储的value值存入自己的寄存器内存中。
b. 这时当线程A被挂起时,B线程会通过compareAndSwapInt()方法
进行比较内存中的value值和自己所存储的value,当两者相等时就会进行内存值value的更新。
c. A线程恢复时,执行compareAndSwapInt()方法
进行比较时会发现value值已经被其他线程修改了。
因为value被volatile修饰,所有B线程对内存的修改A线程是可以看到的
?有问题
- CAS操作存在的问题
a. ABA
问题
- 线程初次读取特定内存位置的内容为A,在准备赋值时检查到还是A,能说明值没有被其他线程修改过吗?
首先:在ABA后,当前值与内存值比较结果相同的,如果在进行交换更新内存值后。为什么这个过程是有问题的?
如上图1是一个单向链表实现的堆栈,栈顶为A,线程1已经直到A.next为B,希望用CAS操作将栈顶替换为B。
在线程1进行操作之前,线程2将A、B出栈,将D、C、A push进栈,这时如图2所示。
当线程1再继续CAS操作将栈顶替换为B时,检测到栈顶还是A,这时候CAS操作会成功,栈顶变为B。
此时如图3所示,B.next = null,堆栈中只有一个B元素,会将C、D丢掉。
这就是为什么在ABA后当前值与内存值比较结果相同的,再进行更新内存值的这个过程是有隐患的有问题的原因。
怎么解决ABA问题?
思路:给值加一个修改版本号,每次值变化都会修改值得版本号,每次得CAS操作时都对比版本号。
Java中使用AtomicStampedReference
类来解决ABA问题。AtomicStampedReference类中的内部类Pair中主要包含一个对象引用以及一个可以自动更新的整数stamp
去解决ABA问题。
AtomicStampReference的部分源码
public class AtomicStampedReference {
private static class Pair {
//对象的引用
final T reference;
//自动更新的版本号
final int stamp;
private Pair(T reference, int stamp) {
this.reference = reference;
this.stamp = stamp;
}
static Pair of(T reference, int stamp) {
return new Pair(reference, stamp);
}
}
private volatile Pair pair;
......
//第一个参数:更新之前的原始值
//第二个参数:将要更新的新值
//第3个参数:期待更新的版本标志
//第4个参数:将要更新的版本标志
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
//获取当前pair
Pair current = pair;
//如果
//原始值等于当前pair的引用,则说明值未变化
//原始标记等于当前pair的标记版本,说明版本未变化
//新值等于旧值并且新版本等于旧版本就什么也不干
//否则CAS操作,生成一个新的pair对象,替换掉旧的,修改成功返回true,失败返回false
return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
}
b. 循环时间开销大
CAS操作长时间不成功时会导致线程一直自旋
,给CPU带来很大的开销。
c. 只能保证一个共享变量的原子操作
对一个共享变量操作时,CAS可以保证原子操作;对多个共享变量操作时,CAS无法保证操作的原子性。
DLL?
本地代理
JNI
volatile该看了
为啥对多个共享变量就不可以保证其原子性?
这几点来看:
核心算法原理
Java执行CAS的实践者Unsafe类
并发包Atomic中使用CAS原理实现的并发类
CAS存在的问题