CAS
全称compare and swap,字面意思"比较和交换"
能够比较和交换某个寄存器中的值和内存中的值,看是否相等,如果相等,则把另外一个寄存器中的值和内存中的值进行交换
伪代码
这个就给我们编写线程安全代码,打开了新世界的大门,基于CAS又能衍生出一套"无锁编程",但是CAS的使用范围具有一定的局限性
CAS的应用场景
实现原子类
比如,多线程针对一个count变量进行++操作,在java标准库中,已经有了一组原子类
上述类提供了自增/自减/自增任意值/自减任意值这些操作,就可以基于CAS按照无锁编程的方式来实现
实例代码:
package Thread;
import java.util.concurrent.atomic.AtomicInteger;
public class demo1 {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i= 0;i<5000;i++){
//count++;
count.getAndIncrement();
//++count
//count.incrementAndGet();
//count--;
//count.getAndDecrement();
//--count;
//count.decrementAndGet();
}
});
Thread t2 = new Thread(()->{
for (int i = 0;i<5000;i++){
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count.get());
}
}
上述的原子类就是基于CAS实现的
伪代码示例:
CAS本身是一个单个的指令,这里其实包含了访问内存的操作,当多个cpu尝试同时访问内存的时候,本质上也是会出现先后顺序的
当两个CAS同时访问内存的时候,一定有一个线程的CAS先访问到,另一个后访问到内存的,初始情况下,value是0,然后oldvalue也是零,第一个线程,此时拿到value就是0,会判定成CAS成立,同时更新value为1,第二个线程再进行CAS访问内存,读出来的value就已经是1了,这时候,就会出现value与oldvalue的值是不一样的,就会对old重新赋值
为什么CAS访问内存会有先后呢,这是因为多个CPU在操作同一个资源时,也会涉及到锁竞争(比synchronized代码级别的锁要轻量一些)
实现自旋锁
基于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问题
CAS的关键要点,是比较寄存器1和内存的值,通过这里的是否相等,来判定内存的值,是否发生了变化,如果内存的值变了,存在其他线程进行了修改,
如果内存的值没变,没有别的线程修改,接下来进行的修改就是安全的.
但是,如果这里的值没变,就一定是没有别的线程修改吗?
A-B-A
另一个线程,把变量的值从A->B,又从B->A.此时本线程区分不了,这个值是始终没变,还是变了又改回来了的情况