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这么多线程安全的工具类的都是在它之上实现的,可见它是安全的,至于它的其他用法,就由各位去自行挖掘啦。
本篇到此结束,拜拜!