目录
1.Java的CAS操作利用的是unsafe这个类提供的CAS操作
2.unsafe的CAS依赖的是JVM针对不同操作系统实现的Atomic::cmpxchg
3.Atomic::cmpxchg的实现使用了汇编的CAS操作,并且使用了cpu硬件提供的lock操作来保证原子性
一、什么是CAS
CAS,字面意思就是(比较并交换)。一个CAS涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的值B
1.比较A和V的值是否相等(比较)
2.如果相当,将B的值写入到V(交换)
3.返回操作是否成功
二、CAS伪代码
下面所写的伪代码不是一个原子的操作,真实的CAS操作是一个原子的硬件指令完成的,下面的代码只用于帮助理解CAS。
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
三、CAS是怎样实现的
针对不同的操作系统,JVM用到了不同的CAS实现原理,简单来说:
1.Java的CAS操作利用的是unsafe这个类提供的CAS操作
2.unsafe的CAS依赖的是JVM针对不同操作系统实现的Atomic::cmpxchg
3.Atomic::cmpxchg的实现使用了汇编的CAS操作,并且使用了cpu硬件提供的lock操作来保证原子性
简而言之,是因为硬件上提供了支持,软件层面上才得以实现。
四、CAS的应用
1.实现原子类
标准库中java.util.concurrent.atomic包中里面的类都是通过这种方式实现的。
典型的AtomicInteger类其中的getAndIncrement()就相当于i++的操作。
AtomicInteger i=new AtomicInteger(0);
//相当于i++
i.getAndIncrement();
伪代码实现:
class AtomicInteger {
private int value;
public int getAndIncrement() {
int oldValue = value;
while ( CAS(value, oldValue, oldValue+1) != true) {
oldValue = value;
}
return oldValue;
}
}
假设两个线程同时调用getAndIncrement():
1.两个线程都读取 value 的值到 oldValue 中. (oldValue 是⼀个局部变量, 在栈上. 每个线程有⾃⼰的栈)
2.线程1先进行CAS操作,由于oldValue的值和value的值相同,直接对value进行赋值。
注意:
CAS是直接读内存的,而不是操作寄存器
CAS的读内存,比较,写内存是原子性的,是一条硬件指令
3.线程2 再执⾏ CAS 操作, 第⼀次 CAS 的时候发现 oldValue 和 value 不相等, 不能进⾏赋值. 因此需要 进⼊循环. 在循环⾥重新读取 value 的值赋给 oldValue
4.线程2第二次执行CAS操作,此时value的值和oldValue的值相等,于是直接执行赋值操作
5.线程1 和 线程2 返回各⾃的 oldValue 的值即可.
2.实现自旋锁
基于CAS实现更灵活的锁,获取更多的控制权。
自选锁伪代码:
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就⾃旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
CAS的ABA问题
1.什么是ABA问题?
2.ABA问题带来的BUG
大部分情况下t2线程这样的一个反复横跳的修改,对t1线程对num的修改是没有影响的,但是有一些特殊情况:
假设我们有100的存款,我们想从ATM机中取50块钱。取款机创建了两个线程, 并发的来执⾏ -50 操 作. 我们期望⼀个线程执⾏ -50 成功, 另⼀个线程 -50 失败.