还记得以前看源码的时候,第一次看到Unsafe类的时候的"震撼感"。首先是这个名字 Unsafe 就有种让人敬而远之的感觉,其次进去看了一下还大多都是native方法,还不给注释,有种根本不想让人探究里面的神秘的感觉。。因此对它的印象就是:这个类肯定很厉害,有一种在游戏里面隐藏在地图不起眼角落的神秘而强力的BOSS的感觉,然后主角人物有一天莫名巧合的来到了这里之后,天空突然出现了神秘的声音:这里不是你能来的地方,赶快离开!(x)

CAS操作

Java并发体系中,锁占据了一席之地。但是有时候锁带来的开销有些过大,因此Java还提供了volatile关键字来保证共享变量的可见性。不过,它也不能解决原子性问题,这时候就需要CAS(compare and swap)相关方法了。

Java的Unsafe类中提供了许多CAS方法,他们都是硬件级别的原子操作,并且都是通过JNI的方式访问C++库,以本地方法来实现的。

下面列举几个方法

方法名说明
boolean compareAndSwapLong(Object obj, long offset, long expect, long update)比较对象obj中偏移量为offset的变量的值是否与expect相等,相等则使用update更新后返回true,否则返回false
long getAndSetLong(Object obj, long offset, long update)获取对象obj中偏移量为offset的变量volatile语义的当前值,并使之变量volatile语义的值为update
long getAndAddLong(Object obj, long offset, long addValue)获取obj中偏移量为offset的变量volatile语义的当前值,并对原始值加上addValue
void park(boolean isAbsolute, long time)阻塞当前线程,第一个变量表示第二个变量是绝对时间还是相对时间
void unpark(Object thread)唤醒调用park后阻塞的线程
void putLongvolatile(Object obj, long offset, long value)设置obj中偏移量为offset的变量的值为value
public native long getLongvolatile(Object obj, long offset)获取对象obj中偏移量为offset的变量对应的volatile语义值

对于其中第三个方法,我们看一下源码

public final long getAndSetLong(Object o, long offset, long newValue) {
    long v;
    do {
        v = getLongVolatile(o, offset);
    } while (!weakCompareAndSetLong(o, offset, v, newValue));
    return v;
}

主要思路就是,先获取当前变量的值,然后使用CAS原子操作设置新值

使用了do while循环来确保多线程调用情况下CAS失败时需要的重试

尝试使用Unsafe类

Unsafe类使用了单例设计模式,我们需要使用getUnsafe方法来获取Unsafe对象

public final class Unsafe {
    private static final Unsafe theUnsafe;
    private Unsafe() {
    }
    static {
        registerNatives();
        Reflection.registerMethodsToFilter(Unsafe.class, new String[]{"getUnsafe"});
        theUnsafe = new Unsafe();
        //省略...
    }
    public static Unsafe getUnsafe() {
        //...省略
        return theUnsafe;
    }
}
public class TestUnSafe {

    static final Unsafe unsafe = Unsafe.getUnsafe();

    static long stateOffset = 0;

    static {
        try {
            stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    private final long state = 0;
    
    public static void main(String[] args) {
        TestUnSafe test = new TestUnSafe();
        Boolean success = unsafe.compareAndSwapInt(test, stateOffset, 0, 1);
        System.out.println(success);
    }
}

这里使用的CAS操作为,将我们定义的state值改为1,但是运行之后报了个异常

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.SecurityException: Unsafe
    at sun.misc.Unsafe.getUnsafe(Unsafe.java:90)
    at unsafe.TestUnSafe.<clinit>(TestUnSafe.java:7)

我们去看一下getUnsafe()方法

public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

这里进行了一个判断,如果调用getUnsafe方法的对象的Class是不是由启动类加载器(Bootstrap)所加载的,如果不是的话就直接抛出一个SecurityException

很明显我们写的这个类是由应用程序类加载器(AppClassLoader)加载的

这里做了限制的原因是,如果我们的应用程序可以随便使用Unsafe来直接修改内存的话,是非常不安全的,很容易误伤到各种核心的变量导致崩溃,这是绝对不允许的,因此JDK的开发人员做了这个限制,不让我们来使用。

绕过限制

通过万能的反射可以绕过这个限制

public class TestUnSafe {

    static Unsafe unsafe = null;
    static long stateOffset = 0;
    private volatile long state = 0;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            stateOffset = unsafe.objectFieldOffset(TestUnSafe.class.getDeclaredField("state"));
        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
            throw new Error(e);
        }
    }

    public static void main(String[] args) {
        TestUnSafe testUnSafe = new TestUnSafe();
        Boolean success = unsafe.compareAndSwapInt(testUnSafe, stateOffset, 0, 1);
        System.out.println(success);
        System.out.println(testUnSafe.state);
    }

}

输出:

true
1

可以发现我们成功调用Unsafe的CAS方法来修改state的值为1了

Last modification:June 23rd, 2021 at 10:10 pm