JDK8---UnSafe类实现CAS乐观锁

CAS:CompareAndSwap(比较并交换),简单点说,内存地址V,旧值为A,当要修改为新值B的时候,先判断V当前的值是不是A,如果是,则将V的值修改为B,否则失败。

那么JDK8中是怎么实现的呢?(ConCurrentHashMap、Atomic、AQS等常见并发类的底层实现都有它)

在sun.misc包下有一个 UnSafe类(在rt.jar包下面):

public final class Unsafe {
private Unsafe() {//静态构造方法
    }

    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {//主要判断类的加载器
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }
}

构造方法私有,压根不让用。唯一暴漏的getUnsafe方法也不能使用。通过Reflection.getCallerClass() 拿到当前调用方法的实际类,从下面代码可以看到只要实际类的类加载器不为null,直接抛异常。因为rt.jar是由 bootstrap类加载器 来加载的,在Java中看不到的(null),而我们的代码基本是由 AppClassLoader 加载的。

public static boolean isSystemDomainLoader(ClassLoader loader) {
        return loader == null;
    }

那怎么办呢?Java的重量级选手-反射 上场了。通过反射,很简单就拿到类Unsaf的实例。

Field f = Unsafe.class.getDeclaredFields()[0];
f.setAccessible(true);
Unsafe u = (Unsafe) f.get(Unsafe.class);

我们拿AtomicInteger来分析它的用法:

private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

//以原子方式将当前值增加一
public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

AtomicInteger类属于rt.jar包,所以可以直接getUnsafe()。再来看unsafe.getAndAddInt(),总共有三个参数,第一个是当前对象,第二个是valueOffset(下面分析),第三个 加1。

valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value")) 这个很重要,Unsafe的CAS操作都是建立在它之上的

从方法名称上来看 对象字段的偏移量(后面会详细举例说明),因为一个Java对象的大小在类加载完成后是可完全确定的。

public final int getAndAddInt(Object obj, long offSet, int a) {
        int b;
        do {
            b= this.getIntVolatile(obj, offSet);
        } while(!this.compareAndSwapInt(obj, offSet, b, b+ a));

        return var5;
    }

public native int getIntVolatile(Object obj, long offSet);

public final native boolean compareAndSwapInt(Object obj, long offSet, int A, int B);

上面代码的参数名称做了部分修改,看起来更加清楚。

getIntVolatile方法->获取obj对象内存地址上偏移量offSet地址上属性的int值

compareAndSwapInt方法->设置obj对象内存地址上偏移量offSet地址上属性的值,如果当前属性值为A,则改为B返回true,否则返回false。

下面来看测试代码,根据结果一步步验证我们的想法:

public class CasTest {
    private static final long SIZE;
    private static Unsafe u;

    static {
        try {
            Field f = Unsafe.class.getDeclaredFields()[0];
            f.setAccessible(true);
            u = (Unsafe) f.get(Unsafe.class);
            SIZE = u.objectFieldOffset
                    (CasTest.class.getDeclaredField("size")); //获取字段在CasTest对象上的偏移量
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    private int size = 2;

    public void t() {
        System.out.println(size);
        if (u.compareAndSwapInt(this, SIZE, 2, 1))// 比较的是this内存上 偏移量为 SIZE,在这里实际就是指size。期望值是2,想改成1
            System.out.println(true);
        System.out.println(size);
        size = 3;
        if (u.compareAndSwapInt(this, SIZE, 3, 7)) // 实际修改size = 7,不是改的SIZE
            System.out.println(true);
        System.out.println(SIZE);
        System.out.println(size);
    }
}

最终结果为:

2
true
1
true
12
7

Unsafe虽然从名字看不安全,Java这么多线程安全的工具类的都是在它之上实现的,可见它是安全的,至于它的其他用法,就由各位去自行挖掘啦。

本篇到此结束,拜拜!

上一篇:2021-05-14


下一篇:漫画:什么是CAS机制?(进阶篇)