CAS自旋锁

四、 CAS自旋锁(Compare And Swap)

思考一个问题:i++是否是原子性的?

分析i++的操作过程:

  1. 内存读取数据写到寄存器
  2. 寄存器进行自增操作
  3. 寄存器将值写回内存

经过上面分析可以知道,i++不是原子性的。那么如何使用多线程进行i++操作保证原子性?

上一节学习了synchronized可以保证原子性,因此我们可以使用synchronized实现:

import java.util.concurrent.TimeUnit;

/**
 * @author 赵帅
 * @date 2021/1/13
 */
public class SynchronizedIncrementDemo {

    private Integer num = 0;

    public void fun1() {
        synchronized (this) {
            for (int i = 0; i < 1000; i++) {
                num++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SynchronizedIncrementDemo demo = new SynchronizedIncrementDemo();

        for (int i = 0; i < 10; i++) {
            new Thread(demo::fun1).start();
        }

        TimeUnit.SECONDS.sleep(1);
        System.out.println(demo.num);
    }
}

synchronized是通过加锁的方式保证原子性的。其实在操作系统底层是有CAS原语来保证原子性的。

AtomicInteger

AtomicInteger就是通过CAS实现的,上面代码用AtomicInteger实现为:

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

/**
 * @author 赵帅
 * @date 2021/1/13
 */
public class AtomicIncrementDemo {
    private static AtomicInteger num = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    num.getAndIncrement();
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(1);
        System.out.println("num = " + num);
    }
}

什么是CAS

compare and swap比较并交换

在使用synchronized保证原子性时,是多个线程竞争同一把锁,同一时刻只有一个线程执行代码,以此保证原子性,但是当加上synchronized时就相当于多线程串形执行了,效率可能会变低(synchronized经过锁升级后,效率提高很多,并不一定比CAS低)。除了synchronized可以保证原子性,在cpu级别有compxchg指令,这个指令也可以保证原子性。

执行 compxchg指令的时候,会判读当前系统是否为多核CPU,如果是多核CPU,那么就会给总线加锁,保证只有一个线程能给总线加锁成功,加锁之后进行CAS操作,因此可以说CAS也是排他锁,不过相比synchronized,CAS属于CPU原语级别,因此多线程情况下效率会高一点。

CAS的工作原理

当我们要执行一个i++的操作时,如果用CAS去执行的话,那么执行的过程,大概就是:

  1. i放在内存中,值为1
  2. 加载i到cpu中,并设置为旧的值old,载进行+1计算的到新的值new
  3. 再次拿内存中的i值,得到一个期望值expect。
  4. 然后我们拿expect与old值就行比较,如果相等的话,就说明这个值没有在我计算的过程中没有被改变过,那么就将i的值更新为新的值。如果expect与old不相等,那么就说明i的值在我计算的过程中被修改了,那么就重新进入步骤2。

观察上面的流程,会发现整个执行过程会不断的在2~5之间进行循环,直到设置新的值成功为止,这个循环过程,线程并没有进入等待队列,就好像一直在原地转圈,因此CAS也叫自旋锁。

LongAdder

除了使用AtomicInteger,还可以使用LongAdder来保证原子性:

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

/**
 * @author 赵帅
 * @date 2021/1/13
 */
public class LongAdderDemo {

    public static LongAdder num = new LongAdder();
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    num.increment();
                }
            }).start();
        }

        TimeUnit.SECONDS.sleep(1);
        System.out.println("num = " + num);
    }
}

LongAdder是通过分段锁来实现的,因此在数据量非常大的时候,效率会比较高。synchronized,AtomicInteger,LongAdder都可以实现自增操作。他们之间的效率需要根据具体业务来选择,因为Synchronized经过锁升级的过程,效率并不一定比CAS低。

java中还可以通过AtomicReference<>类来创建一个保证原子性的对象。

Unsafe

查看AtomicInteger的getAndIncrement()方法源码:

		public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

可以看到最终使用的是Unsafe类来实现的。

什么是Unsafe

Unsafe是java底层sun.misc包下的一个类,是一个线程不安全的类,可以提供一些用于执行级别低,不安全的操作方法。如:直接访问系统内存资源,自主管理内存资源等。这些方法在提升java运行效率,增强java语言底层资源操作能力方面起到了很大的作用。但是由于Unsafe类使java语言拥有了类似C语言指针一样操作内存空间的能力,因此也增加了内存安全的风险。在程序中过度,不正确的使用Unsafe类,会使得程序出错的概率变大,是的java这个安全的语言变得不再安全。

Unsafe提供的API大致可分为:内存操作,CAS,Class,对象操作,线程调度,系统信息获取,内存屏障,数组操作等

Unsafe的使用

查看Unsafe的源码:

		
		private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        // 仅在引导类加载器 BootstrapClassLoader 加载时才合法
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

可以看出Unsafe是一个单例对象,而且无法通过getUnsafe来获取Unsafe对象,因为只有在BootstrapClassLoader加载器加载时才有效,否则会抛出SecurityExecuption异常。

如果我们想要使用这个类时,可以通过反射来获取 theUnsafe属性来获取Unsafe对象实例。

public class UnsafeDemo {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        field.setAccessible(true);
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println("unsafe = " + unsafe);
    }
}

使用 Unsafe进行CAS操作

在unsafe类中操作CAS的方法有:

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

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

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

原文地址:https://www.cnblogs.com/Zs-book1/p/14284814.html