CAS操作
之前说在java.util.concurrent.atomic包下提供的原子操作类底层使用的是CAS,那么什么是CAS呢,CAS的全称为Compare And Swap,比较并替换,CAS机制中使用了3个基本操作数:内存地址V,旧的预期值A,要修改的新值B
在进行变量更新时,需要对比预期值A和内存地址V中的实际值,如果两者相同,才会将V对应的的值改为B
AtomicInteger为例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
private volatile int value;
static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
|
Unsafe类
CAS的底层使用的是Unsafe类,Unsafe类中的方法都是native方法,看下提供了哪些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public native long objectFieldOffset(Field field);
public native int arrayBaseOffset(Class<?> arrayClass);
public native int arrayIndexScale(Class<?> arrayClass);
public final native boolean compareAndSwapInt(Object obj, long offset, int except, int update);
public native Object getObjectVolatile(Object obj, long offset);
public native void putObjectVolatile(Object obj, long offset, Object value);
public native void park(boolean isAbsolute, long time);
public native void unpark(Object thread);
|
Unsafe类不可以直接使用,因为Unsafe会判断当前的类加载器是不是Bootstrap类加载器,如果不是的话,会抛出异常SecurityException
1 2 3 4 5 6 7 8
| public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
|
既然不能直接使用,那么只能使用反射来使用Unsafe类了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class TestUnsafe { static final Unsafe unsafe;
static final long valueOffset;
private volatile int value = 0;
static { try { Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); theUnsafe.setAccessible(true);
unsafe = (Unsafe) theUnsafe.get(null);
valueOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("value")); } catch (NoSuchFieldException | IllegalAccessException e) { e.printStackTrace(); throw new RuntimeException(""); }
}
public static void main(String[] args) { TestUnsafe testUnsafe = new TestUnsafe();
unsafe.getAndAddInt(testUnsafe, valueOffset, 1); System.out.println(testUnsafe.value); } }
|
CAS的缺陷
虽然CAS采用的无锁操作来提供性能,但是CAS并不是完美的,存在了很多的不足
- CPU开销大,在高并发的情况下,如果许多线程反复尝试更新某一个变量,却又一直更新不成功,一直循环下去,会给CPU带来很大的压力
- 不能保证代码块的原子性,CAS只能保证一个变量的原子性操作,而不能保证整个代码块的原子性
- ABA问题,由于CAS对比的是两个最终值,所以可能会导致中间过程中值变化无法感知,变量的值从A变成B,然后再从B变成A,构成了环形转换
ABA问题的解决
解决这个问题很简单,ABA的本质就是无法感知中间过程,那么加一个版本号就可以了,每次版本号递增1,在比较时,不仅要比较内存地址V和旧的预期值A,还要比较一下版本号
在JDK中AtomicStampedReference类给每个变量都配备了一个时间戳stamp,从而避免了ABA问题的产生
1 2 3 4 5
| int stamp = 1;
AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<>(0,stamp);
stampedReference.compareAndSet(0,1,stamp,stamp+1);
|