一、CAS操作
无锁实现多线程并发的原理即为CAS操作,也可以称为“乐观锁”。其中的关键是compareAndSet,它的简称是 CAS(也可以称为Compare And Swap),它必须是原子操作。CAS翻译为:比较并设置/比较并交换
public void withdraw(Integer amout) {
while(true){
/*
prev是取出的余额值,也是执行cas操作时的预期值,如果从内存中取出的最新值与之前拿出来的预期值不同,
则cas失败
*/
int prev=balance.get();
//要修改后的余额
int next=prev-amout;
//真正修改
if(balance.compareAndSet(prev,next)){
break;
}//修改前和修改后的值传入
}
}
}
其中 prev 是共享变量-账户余额中获取的值,也是CAS操作时的预期值,next 是对共享变量-账户余额的修改值,compareAndSet操作即传入 预期值+要修改的值 两个变量。
CAS操作原理:
如下过程所示,当线程1想要对共享变量的值修改时,cas操作传入:cas(100,90),这时要再次访问一次共享变量的最新值,如果发现最新值和传入的预期值(即100)不同,那么此次cas操作则失败,经过while循环等待下次的cas更新。最下边的cas操作传入:cas(90,80),如果查询到最新值是90,说明此次可以修改,那么此次cas操作成功,break退出循环,方法运行结束。
注意
CAS的底层是 lock cmpxchg 指令(X86架构),在单核CPU和多核CPU下都能够保证【比较-交换】的原子性,所以CAS是原子操作,不用担心线程安全问题。所以无锁实现多线程并发实际是线程安全的,因为本例中CAS前均为读操作,关键的对共享变量的写操作还被封装在了CAS操作中,而CAS是原子性的,所以线程安全。
二、CAS与volatile
CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果,因为如果共享变量不加volatile修饰,那么CAS操作时即有可能读不到最新值,那么CAS操作就会变得无意义。
无锁实现多线程并发的例子中,AtomicInteger类内部将值自动转成 volatile 修饰的变量:
public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
//....各种方法
}
总结:
所以无锁实现并发关键在于两个点:
- 共享变量使用原子变量/原子引用修饰,以保证对共享变量访问的原子性,从而使得线程安全。
- CAS操作与volatile结合,以保证多线程运行结果的正确性
综上,无锁并发,共享变量用原子变量/原子引用修饰即可,至于方法定义:
- 如果是原子变量,对其操作调用自带的基础方法(如getAndAdd等)
- 如果是原子引用,对其操作的方法采用原始CAS操作自定义实现即可