CAS与自旋锁

CAS、自旋锁是什么

CAS即Compare and Swap,是一种比较并交换算法

自旋锁是一种基于CAS的锁,获取锁的线程不会被阻塞,而是循环的去获取锁

 

CAS的原理

Unsafe是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native) 方法来访问,Unsafe相当于一个后门,基于该 类可以直接操作特定内存的数据。Unsafe 类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为Java中 CAS操作的执行依赖于Unsafe类的方法。

CAS有三个操作值:内存值V、预期值A与修改值B。只有当预期值A与内存值V相同时,才会将内存值替换成B。否则,会进入下一轮循环中。

意思就是,假如有一个共享变量O,它的值为1,现在有两个线程A、B去获取修改O,线程A、B获取到O=1,A想要将O修改为2,B想要将O修改为3。A跑得比较快,A拿自己的副本O(值为1)与真实的O(值为1)进行比较,相等,修改成功。然后线程B去将O修改为3,先比较值,B的副本O(值为1)与真实O(值为2)比较,不相等,修改失败。

 

CAS的优点与缺点

优点:

       在并发量不是很高的时候可以提高效率。

缺点:

      1、如果长时间循环对CPU的开销很大。

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

      3、ABA问题

ABA问题:

  线程A比线程B快得多,此时:

          A将O=10修改为20

          A将O=20修改为10

          B在修改O值时,本应该修改失败,但是因为A将值修改过之后又改回来了,导致B修改成功了,但是在有些业务场景下是不允许这么操作的。

如何解决ABA问题?

  对内存中的O值添加一个版本号,在比较值的同时还要比较版本号。

  AtomicStampedReference就使用了版本号实现CAS。

 

自旋锁实例

public class Test1 {

    public static void main(String[] args) {

        Test1 test1 = new Test1();

        new Thread(() -> {
            test1.lock();
            test1.business();
            test1.unLock();
        }, "线程A").start();

        // 确保A先开始
        try { TimeUnit.MILLISECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {
            test1.lock();
            test1.business();
            test1.unLock();
        }, "线程B").start();
    }

    AtomicReference atomicReference = new AtomicReference<>(); // 初始值为null,相当于内存值为null

    public void lock(){

        System.out.println(Thread.currentThread().getName() + "开始加锁");
        Thread thread = Thread.currentThread();

        while (!atomicReference.compareAndSet(null, thread)){ // 预期值为null,如果与内存值相等,则替换内存值为thread
            System.out.println(Thread.currentThread().getName() +"自旋中");
        }
        System.out.println(Thread.currentThread().getName() + "加锁成功");
    }

    public void unLock(){

        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "解锁");

        atomicReference.compareAndSet(thread, null); // 预期值为thread,如果与内存值相等,则替换内存值为null
    }

    public void business(){
        System.out.println(Thread.currentThread().getName() + "开始执行业务");
        try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "执行完成");
    }
}


/*********************************控制台打印结果*********************************/
线程A开始加锁
线程A加锁成功
线程A开始执行业务
线程B开始加锁
线程B自旋中
线程B自旋中
线程B自旋中
 ......
线程B自旋中
线程A执行完成
线程A解锁    
线程B自旋中
线程B加锁成功
线程B开始执行业务
线程B执行完成
线程B解锁

 

你可能感兴趣的:(java)