还记得以前看源码的时候,第一次看到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了