一、CAS操作

无锁实现多线程并发的原理即为CAS操作,也可以称为“乐观锁”。其中的关键是compareAndSet,它的简称是 CAS(也可以称为Compare And Swap),它必须是原子操作。CAS翻译为:比较并设置/比较并交换

  1. public void withdraw(Integer amout) {
  2. while(true){
  3. /*
  4. prev是取出的余额值,也是执行cas操作时的预期值,如果从内存中取出的最新值与之前拿出来的预期值不同,
  5. 则cas失败
  6. */
  7. int prev=balance.get();
  8. //要修改后的余额
  9. int next=prev-amout;
  10. //真正修改
  11. if(balance.compareAndSet(prev,next)){
  12. break;
  13. }//修改前和修改后的值传入
  14. }
  15. }
  16. }

其中 prev 是共享变量-账户余额中获取的值,也是CAS操作时的预期值,next 是对共享变量-账户余额的修改值,compareAndSet操作即传入 预期值+要修改的值 两个变量。

CAS操作原理:

如下过程所示,当线程1想要对共享变量的值修改时,cas操作传入:cas(100,90),这时要再次访问一次共享变量的最新值,如果发现最新值和传入的预期值(即100)不同,那么此次cas操作则失败,经过while循环等待下次的cas更新。最下边的cas操作传入:cas(90,80),如果查询到最新值是90,说明此次可以修改,那么此次cas操作成功,break退出循环,方法运行结束。
image.png

注意

CAS的底层是 lock cmpxchg 指令(X86架构),在单核CPU和多核CPU下都能够保证【比较-交换】的原子性,所以CAS是原子操作,不用担心线程安全问题。所以无锁实现多线程并发实际是线程安全的,因为本例中CAS前均为读操作,关键的对共享变量的写操作还被封装在了CAS操作中,而CAS是原子性的,所以线程安全。

二、CAS与volatile

CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果,因为如果共享变量不加volatile修饰,那么CAS操作时即有可能读不到最新值,那么CAS操作就会变得无意义。

无锁实现多线程并发的例子中,AtomicInteger类内部将值自动转成 volatile 修饰的变量:

  1. public class AtomicInteger extends Number implements java.io.Serializable {
  2. private static final long serialVersionUID = 6214790243416807050L;
  3. // setup to use Unsafe.compareAndSwapInt for updates
  4. private static final Unsafe unsafe = Unsafe.getUnsafe();
  5. private static final long valueOffset;
  6. static {
  7. try {
  8. valueOffset = unsafe.objectFieldOffset
  9. (AtomicInteger.class.getDeclaredField("value"));
  10. } catch (Exception ex) { throw new Error(ex); }
  11. }
  12. private volatile int value;
  13. //....各种方法
  14. }

总结:

所以无锁实现并发关键在于两个点:

  • 共享变量使用原子变量/原子引用修饰,以保证对共享变量访问的原子性,从而使得线程安全。
  • CAS操作与volatile结合,以保证多线程运行结果的正确性

综上,无锁并发,共享变量用原子变量/原子引用修饰即可,至于方法定义:

  • 如果是原子变量,对其操作调用自带的基础方法(如getAndAdd等)
  • 如果是原子引用,对其操作的方法采用原始CAS操作自定义实现即可