CAS
全称:Compare and swap,能够比较和交换某个寄存器中的值和内存中的值,看是否相等,如果相等,则把另外一个寄存器中的值和内存进行交换.
(这是一个伪代码,所以这里的&address实际上是想要表示取出address中的值)
那么我们可以看到,CAS就是这样一个简单的交换操作,那么我们为什么要在这里单独提出来呢?
原因是:这一段逻辑是通过一条CPU指令来完成的!(这就意味着,它是原子的)
这个对于编写线程安全的代码是非常重要的.我们为什么会有线程安全问题?归根结底是因为当前操作不是原子的.基于CAS又可以衍生出一套"无锁编程",但是CAS的使用范围具有一定的局限性.加锁的适用范围更广.
CAS的应用
实现原子类
比如,多线程针对一个count变量进行++操作,在Java标准库中,已经提供了一组原子类
//原子类的使用
public class Demo {
public static AtomicInteger count =new AtomicInteger();
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();//相当于--count;
}
});
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访问内存会有先后呢?多个CPU在操作同一个资源,也会涉及到锁竞争(指令级别的锁),指令级别的锁是比synchronized代码级别的锁要轻量很多的.
实现自旋锁
CAS的ABA问题
CAS的关键要点是比较寄存器1和内存的值,通过这里的值是否相等来判定内存的值是否发生了变化,如果内存的值变了,那么就存在其他线程对其进行了修改,如果内存的值没有变,那么就说明没有其他线程进行修改,接下来进行的修改就是安全的.
如果这里的值没有变,就一定没有别的线程进行修改吗?
A-B-A
另一个线程,把变量的值从A->B,又从B->A.此时本线程就区分不了,这个值是始终没变还是出现变化有变回来了的这种情况.大部分情况下,就算出现ABA问题也没事.
在面对ABA问题,CAS的基本思路是正确的,但是主要是修改操作能够进行反复横跳,就容易让咱们CAS的判定失效.CAS判定的是"值相同",实际上期望的是"值没有变化过"如果约定,值只能单向变化,那不就可以判断了吗?有的时候我们的数据不允许只增加不减少,此时我们只需要在增加一个变量来专门记录修改的次数就可以了.
CAS也是属于多线程开发中的一种典型的思路,在实际开发中,一般不会直接使用CAS,都是用库里已经基于CAS封装好的组件(就像原子类这种)
native
见到native关键字,就要明白这个方法是在JVM内部通过C++代码实现的了