什么是CAS:
CAS:Compare And Swap,比较且交换。
CAS中有三个参数:1.内存中原数据的值V 2.预期值A 3.修改后的数据B
Compare:V与A会先比较是否一样
Swap:如果V与A一致,那么就将B写入V
返回操作是否成功
伪代码:
public boolean CAS(int address,int expectValue,int swapValue){ while(address == expectValue){ address = swapValue; return true; } return false; }
值得注意的是:CAS并不是靠一段代码实现的,它其实是cpu里的一条指令完成的,且操作是原子性的,这就在一定程度上解决了线程安全的问题,故在以往加锁的基础上,又有一个新的选择来规避线程安全问题了
原子类:Atomic
自增操作伪代码:
class MyAtomicInteger{ int value; public int getAndIncrement(){ int oldValue = value; while(CAS(value,oldValue,oldValue+1) != true){ oldValue = value; } //后置++ 故不是返回+1后的值 return oldValue; } }
Java标准库中的atomic下的原子类,就是通过CAS来完成自增自减的操作的,此时不需要加锁,也是线程安全的
public static void main(String[] args) throws InterruptedException { //原子类 基于CAS完成自增自减的操作 不用加锁 也是线程完全的 AtomicInteger count = new AtomicInteger(0); Thread t1 = new Thread(() -> { for(int i = 0; i < 50000; i++){ count.getAndIncrement(); // count++ } }); Thread t2 = new Thread(() -> { for(int i = 0; i < 50000; i++){ count.getAndIncrement(); //count++ } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(count); }
我们来查看一下自增操作方法的源码:
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); } public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; } public native int getIntVolatile(Object var1, long var2);
ps:native表示的是本地方法,其实现细节内容是由c++写的虚拟机中实现的
代码中的compareAndSwapInt就是我们上面所说的CAS操作了
自旋锁:
伪代码
public class mySpinLock { //自旋锁实现伪代码 private Thread owner = null; public void lock(){ while(!CAS(this.owner,null,Thread.currentThread())){ //通过CAS可以知道当前锁是否被线程所有 //如果此时锁已经被其他线程所有,那么此线程就会自旋等待CAS = false //如果此时锁资源是空闲的,那么就会owner设置为当前尝试加锁的线程 } } public void unlock(){ this.owner = null; } }
CAS:检查当前的owner是否为null,为null就交换,将当前线程引用赋值给owner,循环结束,加锁完成,反之就会返回false,继续循环执行
ABA问题:
想象一个极端场景,我们在银行取钱的时候。使用CAS操作,此时有两个线程接收请求。
线程A:如果我有1000元,我取出500块钱,此时余额还是500(这时线程B阻塞)
再我第二次再准备取出五百前,有朋友又给我转了500,此时我的余额又变成了1000。
这个时候线程B开始运行(线程A阻塞):发现余额还是1000,那么久又会触发CAS操作,又给我扣了500块钱,此时卡里的余额只剩500了
这就是ABA的典型场景:值A被修改成了B 值被又被修改成了A 此时此A非彼A了
解决:添加一个版本号或者使用时间戳来判断当前版本,每次修改版本号+1。此时CAS的基准就变成了版本号,就非数据金额了(版本号没有改变,数据就没有被修改)