多线程-CAS

作者: 余生爱静 | 来源:发表于2021-05-21 17:29 被阅读0次

    一、什么是CAS?

    CAS,全称Compare And Swap(比较与交换),解决多线程并行情况下使用锁造成性能损耗的一种机制。
    CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值

    二、CAS的目的

    利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此对于synchronized阻塞算法,J.U.C在性能上有了很大的提升。

    三、CAS(compareAndSwap)的原理探究

    CAS的实现主要在JUC中的atomic包,我们以AtomicInteger类为例:

      /**
         * Atomically increments by one the current value.
         *
         * @return the updated value
         */
        public final int incrementAndGet() {
            return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
        }
    

    通过代码追溯,可以看出JAVA中的CAS操作都是通过sun包下Unsafe类实现,而Unsafe类中的方法都是native方法,由JVM本地实现,所以最终的实现是基于C、C++在操作系统之上操作

    Unsafe类

    java不能直接访问操作系统底层,而是通过本地方法来访问。Unsafe类提供了硬件级别的原子操作,主要提供了以下功能:
    1、通过Unsafe类可以分配内存,可以释放内存;
    类中提供的3个本地方法allocateMemory、reallocateMemory、freeMemory分别用于分配内存,扩充内存和释放内存,与C语言中的3个方法对应。
    2、可以定位对象某字段的内存位置,也可以修改对象的字段值,即使它是私有的;
    3、挂起与恢复
    将一个线程进行挂起是通过park方法实现的,调用 park后,线程将一直阻塞直到超时或者中断等条件出现。unpark可以终止一个挂起的线程,使其恢复正常。整个并发框架中对线程的挂起操作被封装在 LockSupport类中,LockSupport类中有各种版本pack方法,但最终都调用了Unsafe.park()方法。

    public class LockSupport {
        public static void unpark(Thread thread) {
            if (thread != null)
                unsafe.unpark(thread);
        }
    
        public static void park(Object blocker) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            unsafe.park(false, 0L);
            setBlocker(t, null);
        }
    
        public static void parkNanos(Object blocker, long nanos) {
            if (nanos > 0) {
                Thread t = Thread.currentThread();
                setBlocker(t, blocker);
                unsafe.park(false, nanos);
                setBlocker(t, null);
            }
        }
    
        public static void parkUntil(Object blocker, long deadline) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            unsafe.park(true, deadline);
            setBlocker(t, null);
        }
    
        public static void park() {
            unsafe.park(false, 0L);
        }
    
        public static void parkNanos(long nanos) {
            if (nanos > 0)
                unsafe.park(false, nanos);
        }
    
        public static void parkUntil(long deadline) {
            unsafe.park(true, deadline);
        }
    }
    

    4、CAS操作

    /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
         * @return <tt>true</tt> if successful
         */
        public final native boolean compareAndSwapObject(Object o, long offset,
                                                         Object expected,
                                                         Object x);
    
        /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
         * @return <tt>true</tt> if successful
         */
        public final native boolean compareAndSwapInt(Object o, long offset,
                                                      int expected,
                                                      int x);
    
        /**
         * Atomically update Java variable to <tt>x</tt> if it is currently
         * holding <tt>expected</tt>.
         * @return <tt>true</tt> if successful
         */
        public final native boolean compareAndSwapLong(Object o, long offset,
                                                       long expected,
                                                       long x);
    

    四、CAS机制的优缺点

    4.1 优点
    CAS是一种乐观锁,而且是一种非阻塞的轻量级的乐观锁,什么是非阻塞式的呢?其实就是一个线程想要获得锁,对方会给一个回应表示这个锁能不能获得。在资源竞争不激烈的情况下性能高,相比synchronized重量锁,synchronized会进行比较复杂的加锁,解锁和唤醒操作。

    4.2 缺点
    1)循环时间长开销大,占用CPU资源

    2)只能保证一个共享变量的原子操作

    3)ABA问题
    如线程1从内存X中取出A,这时候另一个线程2也从内存X中取出A,并且线程2进行了一些操作将内存X中的值变成了B,然后线程2又将内存X中的数据变成A,这时候线程1进行CAS操作发现内存X中仍然是A,然后线程1操作成功。虽然线程1的CAS操作成功,但是整个过程就是有问题的。比如链表的头在变化了两次后恢复了原值,但是不代表链表就没有变化。

    ABA问题解决方案:
    1、添加版本号
    2、AtomicStampedReference
    java并发包为了解决这个问题,提供了一个带有标记的原子引用类“AtomicStampedReference”,它可以通过控制变量值的版本来保证CAS的正确性。因此,在使用CAS前要考虑清楚“ABA”问题是否会影响程序并发的正确性,如果需要解决ABA问题,改用传统的互斥同步可能会比原子类更高效。

    相关文章

      网友评论

        本文标题:多线程-CAS

        本文链接:https://www.haomeiwen.com/subject/npgjdltx.html