JUC源码解析九:基本数据类型的原子类
一、AtomicInteger
如果你学过JVM内存模型你应该知道原子性以及可见性的概念,在对于一个基本类型的修改中,上述两种原则体现在:
- 一个参数的自增操作其实使用了三条原句方才完成,而在并发条件下,在微观下你不能够保证三条原句能够依次执行而中间没有插入对该参数的其他修改操作。
- 一个字段需要将高速缓存块中的数据刷新到内存中并且使其他CPU高速缓存行中的相应数据失效。
想要详细了解的可以了解下JVM内存模型,此处不再多说
我们先来看看AtomicInteger给我们提供了什么接口:
public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
那么我们来看下AtomicInteger这个类是怎么保证这两个原则的:
2.1、基础字段
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 AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
Unsafe是java提供的获得对对象内存地址访问的类,是JDK中的一个工具,它通过暴露一些Java意义上说“不安全”的功能给Java层代码,来让JDK能够更多的使用Java代码来实现一些原本是平台相关的、需要使用native语言(例如C或C++)才可以实现的功能。
在这一句中:
valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value"));
字面意思是属性偏移量,用这个方法能准确地告诉你某个字段相对于对象的起始内存地址的字节偏移量。 也就是说valueOffset是用来记录value本身在内存的偏移地址的,这个记录主要是为了在更新操作在内存中找到value的位置。
可以注意到的是此处的value被声明为了volatile。就是为了保证在更新操作时,当前线程可以拿到value最新的值。也就是之前说的可见性的保证。
对于元素偏转量的操作我们可以看下以下实例:
int value = 2;
@Test
public void testAtomicIntegerOffset() throws NoSuchFieldException, IllegalAccessException {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true); // theUnsafe是私有变量,因此需要设置访问权限
Unsafe unsafe = (Unsafe) field.get(null);// 获取Unsafe对象,因为是静态对象所以用null
long valueOffset = unsafe.objectFieldOffset(AtomicIntegerTest.class.getDeclaredField("value"));
unsafe.putOrderedInt(this, valueOffset, 3);
unsafe.getAndAddInt(this, valueOffset, 4);
System.out.println(unsafe.getAndSetInt(this, valueOffset, 4));
}
此处输出为7,我们可以看到在AtomicInteger里面unsafe的获取是直接通过Unsafe.getUnsafe()`获取的,那么为什么实例这里要用反射获取呢,原因在这:
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
throw new SecurityException("Unsafe");
如果没有使用BootStrapClassLoader类加载器去加载则会抛出异常。
2.2、内部实现
在AtomicInteger的内部实现中,有很多都是直接调用了Unsafe里面的方法在内存层面上直接修改,由于涉及到的很多的native方法,这里就不再多说。下面就说几个比较有代表性的方法:
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final int updateAndGet(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return next;
}
根据一个定义的操作符来把当前值当第一个参数,传入的参数当做第二个参数更新value,getAndUpdate是返回旧值。updateAndGet是返回新值。 使用CAS自旋的方式,调用了compareAndSet方法,只有返回了true才结束,保证了成功修改。
@Test
public void testAtomicInteger() {
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.set(2);
System.out.println(atomicInteger.getAndUpdate(x -> x += 3));
System.out.println(atomicInteger.get());
}
输出是:
2
5
上述是使用了一个定义操作符,AtomicInteger内部也实现了两个定义操作符的方法:
public final int getAndAccumulate(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return prev;
}
public final int accumulateAndGet(int x,
IntBinaryOperator accumulatorFunction) {
int prev, next;
do {
prev = get();
next = accumulatorFunction.applyAsInt(prev, x);
} while (!compareAndSet(prev, next));
return next;
}
实践一下:
@Test
public void testAtomicInteger() {
AtomicInteger atomicInteger = new AtomicInteger();
atomicInteger.set(2);
System.out.println(atomicInteger.getAndAccumulate(3, (x, y) -> x + y));
System.out.println(atomicInteger.accumulateAndGet(4, (x, y) -> x + y));
}
结果是:
2
9
二、AtomicBoolean
AtomicBoolean是Boolean类型的原子类,里面的实现操作,包括对数据的存储、数据内存偏移量的获取斗鱼AtomicBoolean一样,而不一样的地方则是,Boolean需要对存储的int类型值进行转换,1为true,0为false:
public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}
后面类似的就不再举例。
三、其他类型
其他类型AtomicLong、AtomicReference实现原理基本相似,有兴趣的可以自己看下。