Java 基本功 之 CAS

本文首发于我的公众号《andyqian》, 期待你的关注!

前言

  在Java并发编程中,咱们常常使用锁对竞争资源予以并发控制,以解决资源竞争的问题。但不管是使用 Lock 仍是 Synchronized,随着锁机制的引入,就不可避免的带来另外一个问题,也就锁与解锁时的上下文切换,线程等待 等性能问题。如今回过头来看,在有些场景中,是否真的须要引入锁才能解决竞争资源共享问题?答案是否认的,在JDK源码中,也为咱们实现了。就是今天要介绍的另一种无锁方案-CAS,它大量应用于JUC 包中,也是atomic包中各种的底层原理,其重要行可想而知。java

CAS 简介

  CAS 全称为:Compare And Swap (比较与替换),其核心思想是:将内存值 Value 与指望值 A 进行比较,若是二者相等,则将其设置为新值 B,不然不进行任何操做。CAS操做很是高效,在我看来,其缘由有二,其一:底层调用的是 sun.misc.Unsafe 类,操做的是内存值,很是高效。其二:在多线程环境下,始终只有一个线程得到执行权,未得到执行权的线程并不会挂起而形成阻塞,而是以操做CAS失败后再次执行CAS操做,直至成功,这一个过程,在Java中称之为 “自旋”。面试

源码解析

  Java 中 CAS 应用的十分普遍,幕后英雄是sun.misc.Unsafe 类,单独看Unsafe类的CAS操做可能有些茫然,以咱们熟悉的 AtomicInteger 类中的 compareAndSet 方法为引子,再分析到 Unsafe类可能会更好些。编程

下面为AtomicInteger 类中compareAndSet 方法的源码,以下所述:跨域

1
2
public final boolean compareAndSet(int expect, int update) {
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update)}

 

方法入参中 expect 为指望值, update 为待更新值。多线程

继续往下看,compareAndSet方法内部使用到的是Unsafe.compareAndSwapInt()方法,以下所述:并发

1
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

 

方法入参有四个,其中:cors

  1. Object var1 为对象。
  2. long var2 为 var1 对象的内存地址。
  3. int var4 为 内存地址 中的指望值。
  4. var5 为 待更新的值。

在Unsafe类中,同类的方法有如下几个:性能

1
2
3
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

其实 Unsafe 类还给咱们提供了一系列底层的API,因为篇幅缘由,就再也不展开说明,下次放单独一篇文章中谈谈。this

ABA 问题

   在 CAS 中有一个特别经典的问题,也就是ABA。它说的是:内存值 Value 与指望值 A 进行比较前,Value已经发生过变化了,只不过是其变化后的值也为Value。从而形成从结果上看,其结果一致是一致的,(多发生于多线程条件下)固然这也是符合CAS 条件的。在大多数场景下,咱们并不须要关心这种场景,在须要关心时,咱们也能够使用JDK为咱们提供了实现类 - AtomicStampedReference。在 AtomicStampedReference 类中,引入了标记位的概念,用于标记value值是否被修改过。结合value值 + 标记位是否一致,来判断value值是否修改过。
其源码以下:atom

1
2
3
4
5
6
7
8
9
10
11
12
public boolean compareAndSet(V  expectedReference, // 指望引用对象
                                V newReference,    // 新的引用对象
                                int expectedStamp, //指望标志位
                                int newStamp)  // 新的标识位
       Pair<V> current = pair;  // 获取对象与标识引用对
       return
           expectedReference == current.reference &&   // 指望对象引用是否等于当前引用对象 (是否发生变化)   
           expectedStamp == current.stamp&&      // 指望stamp 是否等于当前stamp 
           ((newReference == current.reference &&   //新的引用对象是否等于当前引用对象,新的stamp是否等于当前stamp
             newStamp == current.stamp) ||
            casPair(current, Pair.of(newReference, newStamp)));  //进行pair 的cas操做

 

其中 pair 为 对象引用与版本标记对象,其源码以下:

1
2
3
4
5
6
7
8
9
10
11
private static class Pair<T> {
       final T reference;
       final int stamp;
       private Pair(T reference, int stamp) {
           this.reference = reference;
           this.stamp = stamp;
       }
       static <T> Pair<T> of(T reference, int stamp) {
           return new Pair<T>(reference, stamp);
       }
   }

 

结语

  在Java 中 CAS 应用的十分普遍,包括但不限于:Atomic,synchorized 底层原理等等。但须要明确的是 CAS 的存在并非用来替换 Lock 的,而是一种互补的关系。日常都在写业务代码,没有更深层次的查看源码,当查看源码时,却又是一件趣事,蛮好的!

 


相关阅读:

1.《CORS跨域实践
2.《说说面试那些事
3.《一个Java小细节!
4.《记一个有趣的Java OOM!

这里写图片描述

 扫码关注,一块儿进步

我的博客: http://www.andyqian.com

相关文章
相关标签/搜索