我们知道了volatile只能保证内存可见性,而保证原子性的synchronized会有锁开销的代价.Java提供了一种非阻塞的原子性操作CAS即compare and swap .其通过硬件保证了获取+比较—+更新三个操作的原子性.
-
CAS:
CAS的设计的思想是先取到当前变量的值,然后比较地址的数据与预期的当前值是否一致,如果一致就修改.不一致就重新取到当前变量的值,然后比较地址的数据与预期的当前值是否一致. -
ABA:
上述设计存在一个问题就是如果线程A取到了变量X=A的值,然后在比较更新前 线程B修改了变量X的值x=B; 线程B修改了变量C的值x=A; 这是比较发现值是相等的做了更新.那么这种情况就违背了原子性了.在修改变量前,其他两个线程都对变量值做了修改. 这个就叫ABA问题,一般解决办法就是在值的基础上增加个版本号或者增加个时间戳作为修改记录. -
unsafe:这里提下unsafe方便理解下面的代码.Unsafe是JDK rt.jar包中的类,类中的方法都是native方法,里面实现了一硬件级别的原子操作. 通过下面代码继续了解cas与unsafe
java中AtomicInteger等就是用CAS+unsafe设计实现的,AtomicInteger是对int类型的数据封装,提供的原子性操作的类:
static class Add {
AtomicInteger ai = new AtomicInteger(0);
public void addAtomicInteger() {
ai.getAndIncrement();
}
public int i=0;
public synchronized void add() {
i++;
}
}
Thread thread0 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++){
add.add();
add.addAtomicInteger();
}
}
});
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++){
add.add();
add.addAtomicInteger();
}
}
});
上面代码中实现了线程安全的int类型的++操作,i变量由synchronized加锁实现,ai变量由AtomicInteger实现.其中addAtomicInteger就是AtomicInteger类提供的int类型的原子性++方法.
一起看下AtomicInteger类中的主要代码
public class AtomicInteger extends Number implements java.io.Serializable {
//JDK提供的底层cas底层实现类 单例的 饿汉模式
private static final Unsafe unsafe = Unsafe.getUnsafe();
//私有的内存可见int变量
private volatile int value;
//数据value在这个类内存空间相对偏移量
private static final long valueOffset;
//
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//构造函数
public AtomicInteger(int initialValue) {
value = initialValue;
}
//原子性+1方法
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
}
objectFieldOffset是unsafe提供的一个返回指定变量在所属类中的内存偏移地址方法.
valueOffset的初始化为什么是静态的代码块?, 静态代码块加载类时只执行一次,第二次new AtomicInteger()不会再执行了. 也就是说valueOffset的值是在当前操作系统下值是固定的
![valueOffset.png](https://img-blog.csdnimg.cn/img_convert/b0d66e8a2ff0e0b8771700eaed333662.png)
valueOffset就是图中区间标识的偏移量.
// 通过反射得到theUnsafe对应的Field对象
Field field = Unsafe.class.getDeclaredField("theUnsafe");
// 设置该Field为可访问
field.setAccessible(true);
// 通过Field得到该Field对应的具体对象,传入null是因为该Field为static的
Unsafe unsafe = (Unsafe) field.get(null);
try {
long value = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
System.out.println(value);
} catch (Exception ex) { throw new Error(ex); }
从代码输出来看value的值每次都是12.
Unsafe实例获取为什么是反射没有用源代码中的
private static final Unsafe unsafe = Unsafe.getUnsafe();
因为类的构造函数是私有的并且提供的获取实例的方法getUnsafe中只允许从引导类加载器(Bootstrap)加载(main方法是AppClassLoader加载)!VM.isSystemDomainLoader(var0.getClassLoader()) 也就是系统自己才能使用. 这也表明了jdk并不希望我们直接使用这个类,从命名也能看出直接名为不安全的类.因为底层有许多危险的直接操作内存不受jvm管控的方法.
@CallerSensitive
public static Unsafe getUnsafe() {
Class var0 = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
} else {
return theUnsafe;
}
}
继续来看原子性加方法,入参是当前对象,int数据相对当前对象地址的偏移量, 加法的加数
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
- this.getIntVolatile(var1, var2):通过内存地址或者int数据的值,并且是内存可见的形式.
- compareAndSwapInt(var1, var2, var5, var5 + var4):入参分别是当前对象,数据相对地址偏移量,通过内存地址取到数据的值,将要修改后的值. 里边是调用的native方法,通过c++等底层保证了(var5与地址值比较,相当情况下修改变量的值 然后返回true)操作的原子性. 如果修改失败就返回fasle. 上层while循环去调用compareAndSwapInt直到修改成功为止. 期间没有阻塞
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);