CAS (自旋锁,无锁,乐观锁,轻量级锁)

1,452 阅读4分钟

Compare And Swap(Set ) ,比较并交换

cas(V,Expected,NewValue)
    if  V == Expected
        V = NewValue
    otherwise 
        try again or fail 

CAS是一条CPU并发原语,它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子性的

CAS并发原语的实现在Java语言中就是sun.misc.Unsafe类的各个方法。调用Unsafe类的方法,Jvm会帮我们实现出CAS汇编指令。

CAS是一种完全依赖于硬件的功能,通过硬件实现了原子操作。

原语属于操作系统用语范畴,由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行过程必须是连续的,在执行过程中不可中断。也就是说CAS作为并发原语是原子指令,不会造成数据不一致的问题。

分析AtomicInteger类 -> getAndIncrement()方法

  • unsafe.getAndAddInt(this, valueoffset, 1)

  • valueoffset:  value变量的内存偏移量,内存地址

  • Unsafe类 -> getAndAddInt(Object var1, long var2, int var4) nm1``

    var1 AtomicInteger对象引用 var2 AtomicInteger对象的value变量的内存偏移量 var4 需要增加的值(1) var5 从根据对象引用和对象的value变量的内存偏移量 从主内存获取到工作内存的value变量最新值赋给变量var5

    value变量在线程工作内存的副本值与 var5 进行比较 如果相同 写回主内存中value的值为:var5 + var4 返回true 如果不同 继续循环{取最新值 -> value变量在线程工作内存的副本值与 var5 进行比较, 直到更新完成结束循环}

  • unsafe.cpp -> atomic::cmpxchg

  • atomic_linux_x86.inline.hpp

  • lock + cmpxchg 2个硬件指令实现原子性

  • lock 缓存行锁/总线锁

Unsafe类是什么

Unsafe类存在于sun.misc包中,其内部方法可以像操作C的指针一样直接操作内存

由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。

Unsafe是CAS的核心类,Java中的CAS操作使用Unsafe类实现。

Unsafe类中所有的方法都是native修饰的,也就是说Unsafe类中所有的方法都直接调用操作系统底层资源执行相应的任务

以AtomicInteger为例,Unsafe通过value变量内存偏移量在主内存中定位到value变量然后进行CAS操作。

CAS的缺点

  1. 可能会消耗大量CPU资源(大量线程CAS的情况下)

  2. 只能保证一个变量的原子操作

  3. ABA问题

ABA问题(狸猫换太子)

ABA问题的产生

CAS算法实现的一个重要前提是某个线程在某个时刻取出内存中的数据并在当下时刻进行比较并交换,但是在这个时间差内“可能数据已经发生了变化A -> B,但又被复原了B -> A

比如说线程t1从内存位置V中取出数据值为A,同时另一个线程t2也从从内存中取出A,并且t2进行了一些操作将内存位置V中的数据值更新为B, 然后t2又将V位置的数据更新为A,紧接着t1进行CAS操作发现内存中仍然是A,t1线程对内存位置V的CAS操作成功

尽管t1的CAS操作成功,但是不代表这个给过程是没有问题的

自旋锁(spinlock)

自旋锁是指获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁

优点:减少线程上下文切换的消耗

缺点:循环会消耗CPU

手写自旋锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * <h1>手写自旋锁</h1>
 */
public class SpinLockTest {
    // 创建原子引用线程
    AtomicReference<Thread> threadAtomicReference = new AtomicReference<>();

    public static void main(String[] args) {
        // 共享资源
        SpinLockTest spinLockTest = new SpinLockTest();

        // AA线程
        new Thread(() -> {
            spinLockTest.myLock();
            try {
                // 暂停5S
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockTest.myUnLock();
        }, "AA").start();

        // main线程保证AA线程先启动
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // BB线程在AA线程睡眠期间会不断自旋(控制台打印 ‘BB spin’)
        new Thread(() -> {
            spinLockTest.myLock();
            spinLockTest.myUnLock();
        }, "BB").start();
    }

    /**
     * <h2>加锁</h2>
     */
    public void myLock() {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + " come in");

        // 循环方式获取锁
        while (!threadAtomicReference.compareAndSet(null, thread)) {
            // 打印没有获取到锁的线程名称
            System.out.println(thread.getName()+" spin");
        }
    }

    /**
     * <h2>解锁</h2>
     */
    public void myUnLock() {
        Thread thread = Thread.currentThread();
        threadAtomicReference.compareAndSet(thread, null);
        System.out.println(thread.getName() + " invoked myUnLock");
    }
}