CAS(Compare and swap)比较并交换算法解析

Posted 沛沛老爹

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CAS(Compare and swap)比较并交换算法解析相关的知识,希望对你有一定的参考价值。

CAS说明

CAS全称是Compare and swap。字面翻译的意思就是比较并交换。

CAS可以用来实现乐观锁,CAS中没有线程的上下文切换,减少了不必要的开销

说明:本文解析的JDK源码为open JDK 11版本。

CAS原理

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

JAVA实现

我们从源码中看下Java实现的 方式。

Java的cas实现代码如下:

public final class Unsafe 

    ...

    @ForceInline
    public final boolean compareAndSwapObject(Object o, long offset,
                                              Object expected,
                                              Object x) 
        return theInternalUnsafe.compareAndSetObject(o, offset, expected, x);
    

    /**
     * Atomically updates Java variable to @code x if it is currently
     * holding @code expected.
     *
     * <p>This operation has memory semantics of a @code volatile read
     * and write.  Corresponds to C11 atomic_compare_exchange_strong.
     *
     * @return @code true if successful
     */
    @ForceInline
    public final boolean compareAndSwapInt(Object o, long offset,
                                           int expected,
                                           int x) 
        return theInternalUnsafe.compareAndSetInt(o, offset, expected, x);
    

    /**
     * Atomically updates Java variable to @code x if it is currently
     * holding @code expected.
     *
     * <p>This operation has memory semantics of a @code volatile read
     * and write.  Corresponds to C11 atomic_compare_exchange_strong.
     *
     * @return @code true if successful
     */
    @ForceInline
    public final boolean compareAndSwapLong(Object o, long offset,
                                            long expected,
                                            long x) 
        return theInternalUnsafe.compareAndSetLong(o, offset, expected, x);
    

    ...

我们可以看到,在JAVA源码中,将compareAndSwap使用了compareAndSwapObject、compareAndSwapInt、compareAndSwapLong等几个方法进行了对应的包装操作。这里对所有的方法使用了@ForceInline注解,说明这里没有使用标准的实现,而是强制使用了HotSpot VM 的优化实现方法。

@ForceInline注解源码

可跳过该小节....

/**
 * A method or constructor may be annotated as "force inline" if the standard
 * inlining metrics are to be ignored when the HotSpot VM inlines the method
 * or constructor.
 * <p>
 * This annotation must be used sparingly.  It is useful when the only
 * reasonable alternative is to bind the name of a specific method or
 * constructor into the HotSpot VM for special handling by the inlining policy.
 * This annotation must not be relied on as an alternative to avoid tuning the
 * VM's inlining policy.  In a few cases, it may act as a temporary workaround
 * until the profiling and inlining performed by the HotSpot VM is sufficiently
 * improved.
 *
 * @implNote
 * This annotation only takes effect for methods or constructors of classes
 * loaded by the boot loader.  Annotations on methods or constructors of classes
 * loaded outside of the boot loader are ignored.
 */
@Target(ElementType.METHOD, ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface ForceInline 

上面@ForceInline的注释的大意是如下:

        假设不需要使用标准指令排列处理时,HotSpot VM的方法或构造器可以使用此注解,让其强制排列。

        这个注解使用的话要谨慎,它唯一合理的用法是用来将其绑定到HotSpot VM的指定的方法或构造器中,用来提供一种排列策略。不能依赖该注解去避免VM的正常指令排列策略,在HotSpot VM中的少数情况下,它只能作为临时的应对方法,直到指令排列情况改善。

这个注解仅对通过boot loader加载的类中的方法和构造器有影响,其他的均会被忽略。

简单来讲,这个注解是用来做自定义指令排列的,它可以在一定程度上避免重排,但是不能作为调整指令排列的核心,只能作为一种替代方案。

compareAndSetXXX相关实现

public final class Unsafe 

    ...

    /**
     * Atomically updates Java variable to @code x if it is currently
     * holding @code expected.
     *
     * <p>This operation has memory semantics of a @code volatile read
     * and write.  Corresponds to C11 atomic_compare_exchange_strong.
     *
     * @return @code true if successful
     */
    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetObject(Object o, long offset,
                                                    Object expected,
                                                    Object x);
    ...

    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetInt(Object o, long offset,
                                                 int expected,
                                                 Object x);
    ...

    @HotSpotIntrinsicCandidate
    public final native boolean compareAndSetLong(Object o, long offset,
                                                  long expected,
                                                  long x);
    ...

上面的这些方法简单的来讲就是如果当前Java的变量是expected值,就自动更新值为x。对应的是C语言11的atomic_compare_exchange_strong标准函数。根据@@HotSpotIntrinsicCandidate注解,我们知道这个实现是HotSpot VM实现的。

@HotSpotIntrinsicCandidate注解

扩展知识,可以略过。

我们知道JDK是统一的代码,不同的JVM的效率会有不同。不同的公司、组织会根据自己的情况,推出不同的JVM。所以这块的优化都会落到VM的肩膀上。每个虚拟机都会使用C或C++对其进行优化或者实现。

HotSpotIntrinsicCandidate是JDK9引入的。@HotSpotIntrinsicCandidate注解可以理解为HotSpot的VM的单独实现(解决)方案,理论上这个实现方式会比JDK的统一的实现方式要高效(这个很自然,你做的效能都比不上官方通用的,那这个解决方案有毛用?)。

HotSpotIntrinsicCandidate源码

/**
 * The @code @HotSpotIntrinsicCandidate annotation is specific to the
 * HotSpot Virtual Machine. It indicates that an annotated method
 * may be (but is not guaranteed to be) intrinsified by the HotSpot VM. A method
 * is intrinsified if the HotSpot VM replaces the annotated method with hand-written
 * assembly and/or hand-written compiler IR -- a compiler intrinsic -- to improve
 * performance. The @code @HotSpotIntrinsicCandidate annotation is internal to the
 * Java libraries and is therefore not supposed to have any relevance for application
 * code.
 *
 * Maintainers of the Java libraries must consider the following when
 * modifying methods annotated with @code @HotSpotIntrinsicCandidate.
 *
 * <ul>
 * <li>When modifying a method annotated with @code @HotSpotIntrinsicCandidate,
 * the corresponding intrinsic code in the HotSpot VM implementation must be
 * updated to match the semantics of the annotated method.</li>
 * <li>For some annotated methods, the corresponding intrinsic may omit some low-level
 * checks that would be performed as a matter of course if the intrinsic is implemented
 * using Java bytecodes. This is because individual Java bytecodes implicitly check
 * for exceptions like @code NullPointerException and @code ArrayStoreException.
 * If such a method is replaced by an intrinsic coded in assembly language, any
 * checks performed as a matter of normal bytecode operation must be performed
 * before entry into the assembly code. These checks must be performed, as
 * appropriate, on all arguments to the intrinsic, and on other values (if any) obtained
 * by the intrinsic through those arguments. The checks may be deduced by inspecting
 * the non-intrinsic Java code for the method, and determining exactly which exceptions
 * may be thrown by the code, including undeclared implicit @code RuntimeExceptions.
 * Therefore, depending on the data accesses performed by the intrinsic,
 * the checks may include:
 *
 *  <ul>
 *  <li>null checks on references</li>
 *  <li>range checks on primitive values used as array indexes</li>
 *  <li>other validity checks on primitive values (e.g., for divide-by-zero conditions)</li>
 *  <li>store checks on reference values stored into arrays</li>
 *  <li>array length checks on arrays indexed from within the intrinsic</li>
 *  <li>reference casts (when formal parameters are @code Object or some other weak type)</li>
 *  </ul>
 *
 * </li>
 *
 * <li>Note that the receiver value (@code this) is passed as a extra argument
 * to all non-static methods. If a non-static method is an intrinsic, the receiver
 * value does not need a null check, but (as stated above) any values loaded by the
 * intrinsic from object fields must also be checked. As a matter of clarity, it is
 * better to make intrinisics be static methods, to make the dependency on @code this
 * clear. Also, it is better to explicitly load all required values from object
 * fields before entering the intrinsic code, and pass those values as explicit arguments.
 * First, this may be necessary for null checks (or other checks). Second, if the
 * intrinsic reloads the values from fields and operates on those without checks,
 * race conditions may be able to introduce unchecked invalid values into the intrinsic.
 * If the intrinsic needs to store a value back to an object field, that value should be
 * returned explicitly from the intrinsic; if there are multiple return values, coders
 * should consider buffering them in an array. Removing field access from intrinsics
 * not only clarifies the interface with between the JVM and JDK; it also helps decouple
 * the HotSpot and JDK implementations, since if JDK code before and after the intrinsic
 * manages all field accesses, then intrinsics can be coded to be agnostic of object
 * layouts.</li>
 *
 * Maintainers of the HotSpot VM must consider the following when modifying
 * intrinsics.
 *
 * <ul>
 * <li>When adding a new intrinsic, make sure that the corresponding method
 * in the Java libraries is annotated with @code @HotSpotIntrinsicCandidate
 * and that all possible call sequences that result in calling the intrinsic contain
 * the checks omitted by the intrinsic (if any).</li>
 * <li>When modifying an existing intrinsic, the Java libraries must be updated
 * to match the semantics of the intrinsic and to execute all checks omitted
 * by the intrinsic (if any).</li>
 * </ul>
 *
 * Persons not directly involved with maintaining the Java libraries or the
 * HotSpot VM can safely ignore the fact that a method is annotated with
 * @code @HotSpotIntrinsicCandidate.
 *
 * The HotSpot VM defines (internally) a list of intrinsics. Not all intrinsic
 * are available on all platforms supported by the HotSpot VM. Furthermore,
 * the availability of an intrinsic on a given platform depends on the
 * configuration of the HotSpot VM (e.g., the set of VM flags enabled).
 * Therefore, annotating a method with @code @HotSpotIntrinsicCandidate does
 * not guarantee that the marked method is intrinsified by the HotSpot VM.
 *
 * If the @code CheckIntrinsics VM flag is enabled, the HotSpot VM checks
 * (when loading a class) that (1) all methods of that class that are also on
 * the VM's list of intrinsics are annotated with @code @HotSpotIntrinsicCandidate
 * and that (2) for all methods of that class annotated with
 * @code @HotSpotIntrinsicCandidate there is an intrinsic in the list.
 *
 * @since 9
 */
@Target(ElementType.METHOD, ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
public @interface HotSpotIntrinsicCandidate 

@HotSpotIntrinsicCandidate只能用于HotSpot虚拟机。它标识这个带注解的方法是HotSpot VM(但不保证是)引入的。如果HotSpot VM用手写的程序集和/或手写的编译器IR -- 一种编译器的内在特性--替换注解的方法以提高性能,则该方法会被激活。

@HotSpotIntrinsicCandidate注解是Java库的内部注解,因此不应与应用程序代码相关。 Java库的维护者在修改使用@HotSpotIntrinsicCandidate注解的方法时必须考虑以下内容:

1、修改使用@HotSpotIntrinsicCandidate注解的方法时,必须更新HotSpot VM实现中的对应内部代码,以匹配注解方法的语义。

2、对于一些带注释的方法,如果使用Java字节码实现内部函数,则相应的内部函数可能会省略一些低级检查。这是因为单个Java字节码隐式地检查诸如NullPointerException和ArrayStoreException之类的异常。如果这样的方法被汇编语言中的内在编码所取代,则作为正常字节码操作执行的任何检查都必须在进入汇编代码之前执行。必要时,必须对内在函数的所有参数以及内在函数通过这些参数获得的其他值(如果有)执行这些检查。可以通过检查方法的非内在Java代码,并准确确定代码可能引发哪些异常,包括未声明的隐式运行时异常,来推断检查。因此,根据本机执行的数据访问,检查可能包括:

  • 对空引用检查
  • 对用作数组索引的原始值进行范围检查
  • 对原始值的其他有效性检查(例如,除以零的情况)
  • 对存储在数组中的存储引用值的检查
  • 对从内在函数索引的数组的数组长度检查
  • 引用强制类型转换(当形参是Object或其他弱类型时)

3、注意,接收值(this)作为额外参数传递给所有非静态方法。如果非静态方法是内在的,则接收器值不需要进行空检查,但(如上所述)也必须检查内在从对象字段加载的任何值。为了清楚起见,最好将Intrinics设为静态方法,以明确对它的依赖关系。此外,最好在输入内部代码之前从对象字段显式加载所有必需的值,并将这些值作为显式参数传递。首先,这对于空检查(或其他检查)可能是必要的。第二,如果内在函数从字段中重新加载值,并在没有检查的情况下对这些值进行操作,则竞争条件可能会将未检查的无效值引入内在函数。如果内在函数需要将值存储回对象字段,则应该从内在函数显式返回该值;如果有多个返回值,编码器应考虑在数组中缓冲它们。从intrinsic中删除字段访问不仅澄清了JVM和JDK之间的接口;它还有助于将热点和JDK实现解耦,因为如果内在函数前后的JDK代码管理所有字段访问,那么内在函数可以被编码为与对象布局无关。

HotSpot VM的维护者在修改内部函数时必须考虑以下几点:

  • 在添加新的内在函数时,请确保Java库中的相应方法用@hospotintrinsicandidate注释,并且导致调用内在函数的所有可能调用序列都包含内在函数忽略的检查(如果有的话)。
  • 修改现有内在函数时,必须更新Java库以匹配内在函数的语义,并执行内在函数所省略的所有检查(如果有)。

不直接参与维护Java库或HotSpot VM的人员可以安全地忽略使用@HotSpotIntrinsicCandidate注解方法的情况。
HotSpot VM定义一个内部函数列表。在HotSpot VM支持的所有平台上,并非所有功能都可用。此外,给定平台上内部函数的可用性取决于HotSpot VM的配置(例如,启用的VM标志集)。因此,用@HotSpotIntrinsicCandidate标识的方法并不能保证HotSpot VM一定被使用(激活)。
如果启用了CheckIntrinsics VM标志,则HotSpot VM会检查(在加载类时)(1)该类的所有方法(也在VM的内部函数列表上)都用@HotSpotIntrinsicCandidate进行注解,并且(2)对于所有用@HotSpotIntrinsicCandidate注解的该类的所有方法在列表中有一个内部函数。 

总结

简单来讲,CAS的原理就是比较值,如果是期望值则替换否则不变。它的实现基本上各大VM都有了实现,很少有自己需要操刀的。

以上是关于CAS(Compare and swap)比较并交换算法解析的主要内容,如果未能解决你的问题,请参考以下文章

CAS(Compare and swap)比较并交换算法解析

CAS Compare and Swap 比较后交换

CAS(Compare and Swap)理解

每日一博 - CAS(Compare-And-Swap)原理剖析

非阻塞同步算法与CAS(Compare and Swap)无锁算法

JUCCAS(Compare And Swap)及其ABA问题