CAS
cas 是 Compare and swap (比较交换 , 比较交换的是内存 和 寄存器)
例如: 有一个内存 M, 和 两个寄存器 A , B
如果 M 和 A 的值相同 , 就把 B 赋值给 M ; 返回 true;
如果 M 和 A 的值不相同 , 就啥都不做 ; 返回 false
伪代码 , address 内存地址 ,
boolean CAS(address , expectvale , swapvalue){ if(&address == expctvale){ &address = swapvalue ; return true ; } return false ; }CAS 的作用
CAS 本质是一个cpu 指令 那么这个操作 就是 原子的 , 可以使用 CAS 来完成一些操作 , 进一步代替 "加锁" ;
给编写线程安全的代码 ,就 引入了新的思路 ; 基于CAS 来实现线程安全的 方式 , 也称为 "无锁编程"
CAS cpu提供的指令 -> 被操作系统封装 , 提供api -> 被jvm封装 ,通过api -> 给我们使用 ;
优点:
保证线程安全, 同时避免阻塞(提供效率)
缺点:
1.代码更复杂 ;
2.只能够适合一些特殊场景, 没有加锁 更加广泛 ;
java中的CAS
基于CAS的原子类 Atomic
用 两个线程 count ++ ,每个线程++5000次, 因为并发编程,导致 最后结果不正确 ;
此时使用 原子类来 , 这样 count ++ ,操作就是原子的 ;
int count 我们 换成 AtomicInteger cont ;
代码
下面这些++操作或--操作 就都是原子的了 ;
//count ++ ; count.getAndIncrement(); // ++ count count.incrementAndGet() ; // count-- ; count.getAndDecrement(); // -- count count.decrementAndGet();
当我们点开这个++的方法,就可以看到 底层代码实现并没有加锁,而是使用了CAS
原子类伪代码实现
AtomicInteger{ int value ; public int getAndIncrement(){ int oldvalue = value ; while( CAS(value , oldvalue , oldvalue+1) != true){ oldvalue = value ; } return oldvalue ; } }线程安全原理: 线程不安全就是因为 操作穿插执行 , CAS++ , 也是防止穿插执行 ;
加锁 ,通过阻塞 ,阻止穿插执行
CAS , 通过 反复的判断重试 , 避免穿插执行 ;
下面的while() 会在 value赋值成功后停止 , 如果value和 oldvalue不同就反复重试 ;
Atomic 里还有很多 原子类,
基于CAS 实现自旋锁
class SpinLock{ Thread owner = null ; // 锁的持有者 ; public void lock(){ // 自旋加锁 while(! CAS (owner , null , Thread.currentThread())){ // 如果owner 锁的持有者 不为空 就 ,自旋等待 // 如果 owner 为空 ,就是锁没有持有者,就立刻让调用加锁的线程马上加上锁 } } public void uLock(){ // 解锁 owner = null ; } }CAS 的 ABA问题
CAS: 是通过 value "没有发生改变" 作为 " 没有其他线程穿插执行" 过 的依据 ;
这种方式 , 不够严谨 , 像我们上面的 A -> B-> A ; 第一个线程a,拿到值 , 然后第二个线程B穿插执行 了 ,修改value的值 ,
对于第一个线程来说 , 好像这个值没变 , 但其实 A线程已经被B线程 穿插执行了 ;
value "没有发生改变" 作为 " 没有其他线程穿插执行" 过 的依据,
CAS ,虽然大部分情况下, 即使被穿插执行, 但是 值又改回去了,(B穿插执行,修改了value, 这边A会把oldvalue改回去);
这里的 情况不一定会产生bug ;
极端情况:
ABA问题 ,一般不会有什么大问题, 但是某些极端情况 会出现bug ;
解决ABA 问题
设置 判定的值value 只能往一个方向进行 变化 ; (例如 加就只能加 , 减就只能减)
如果本来就要有增有减,就引入一个变量 , 版本号 , 每次 value发生改变,就让版本号自增 ;
这样在使用CAS时,就不用直接判断value,而是判断版本号, 看版本号是否有变化 ;