Atomic讲解
文章目录
【注意】最后更新于 March 14, 2017,文中内容可能已过时,请谨慎使用。
引言
Java从JDK1.5开始提供了java.util.concurrent.atomic包,方便程序员在多线程环境下,无锁的进行原子操作。原子变量的底层使用了 处理器提供的原子指令,但是不同的CPU架构可能提供的原子指令不一样,也有可能需要某种形式的内部锁,所以该方法不能绝对保证线程 不被阻塞。
CAS(Compare And Swap)比较并交换
CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量(内存值),E表示预期值(旧的),N表示新值。当且仅当V值等于 E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。 CAS操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出, 并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的 原理,CAS操作即时没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。 CAS 整个操作过程是原子操作,是一条CPU指令完成的
Atomic包介绍
在Atomic包里一共有12个类,如下分几种类型原子类操作:
- 基本类型:AtomicBoolean,AtomicInteger,AtomicLong
- 引用类型:AtomicReference
- 数组类型:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
- 属性原子修改器类型:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater
- 解决ABA类型:AtomicMarkableReference,AtomicStampedReference
Atomic包里的类基本都是使用Unsafe实现的包装类。Unsafe.compareAndSwapXXX来实现原子操作。
|
|
看源码compareAndSwapXXX方法有三个参数,一个参数是一个更新值当前对象,第二参数是修改当前对象属性值偏移量,第三参数当前 属性值(支持类型int,long和Object),第四参数更新值。
第二参数修改属性偏移量获取分两种类型:
-
基本类型和引用类型获取Unsafe.objectFieldOffset(Field field)方法获取。
AtomicInteger源代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13
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); } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
-
数组类型获取相对复杂点。分三步进行
-
第一步:Unsafe.arrayBaseOffset(Class arrayClass)方法获取数组第一个元素的偏移量。
-
第二步:Unsafe.arrayIndexScale(Class arrayClass)方法获取数组类型增量因子。
-
第三步:获取修改数组属性下标偏移值=第一步的数组第一个元素的偏移量+第二步的增量因子(乘以)下标值
AtomicIntegerArray源码实现跟上面说逻辑类似,如下:
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 30 31 32 33 34 35 36 37 38 39
private static final Unsafe unsafe = Unsafe.getUnsafe(); //第一步获取数组第一个元素的偏移量 private static final int base = unsafe.arrayBaseOffset(int[].class); private static final int shift; private final int[] array; static { //第二步获取数组类型增量因子(int:4,long:8,Object:4) int scale = unsafe.arrayIndexScale(int[].class); //数据类型不是2的幂 if ((scale & (scale - 1)) != 0) throw new Error("data type scale not a power of two"); /** *numberOfLeadingZeros(int i) 给定一个int类型数据,返回这个数据的二进制串中从最左边算起连续的“0”的 *总数量。因为int类型的数据长度为32所以高位不足的地方会以“0”填充。 *这里获取一个下标右移增量因子值 * shift int:2,long:3,Object:2 */ shift = 31 - Integer.numberOfLeadingZeros(scale); } private long checkedByteOffset(int i) { if (i < 0 || i >= array.length) throw new IndexOutOfBoundsException("index " + i); return byteOffset(i); } private static long byteOffset(int i) { //第三步获取修改数组属性下标偏移值,这里用到右移增量因子值,我猜想可能性能比乘以(scale*i+base)好点。 return ((long) i << shift) + base; } public final boolean compareAndSet(int i, int expect, int update) { return compareAndSetRaw(checkedByteOffset(i), expect, update); } private boolean compareAndSetRaw(long offset, int expect, int update) { return unsafe.compareAndSwapInt(array, offset, expect, update); }
-
基本类型
原子更新基本类型三个类:
- AtomicBoolean:原子更新布尔类型
- AtomicInteger:原子更新整数类型
- AtomicLong:原子更新长整数类型
分析AtomicInteger方法源码如下:
|
|
注意:lazySet方法,说明解释
lazySet是使用Unsafe.putOrderedObject方法,这个方法在对低延迟代码是很有用的,它能够实现非堵塞的写入,这些写入不会被Java的JIT 重新排序指令,这样它使用快速的存储-存储(store-store) barrier, 而不是较慢的存储-加载(store-load) barrier, 后者总是用在volatile的 写操作上,这种性能提升是有代价的,虽然便宜,也就是写后结果并不会被其他线程看到,甚至是自己的线程,通常是几纳秒后被其他线程看到, 这个时间比较短,所以代价可以忍受。
类似Unsafe.putOrderedObject还有unsafe.putOrderedLong等方法,unsafe.putOrderedLong比使用 volatile long要快3倍左右。.
从AmoticInteger和AmoticBoolean源码分析, 其次AtomicBoolean把Boolean转换成整型,做原子操作。 而都调用Unsafe.compareAndSwapInt方法。 AtomicLong源码分析操作Unsafe.compareAndSwapLong方法。
引用类型
AtomicReference:原子操作更新引用类型
AtomicReference源码分析调用Unsafe.compareAndSwapObject方法。
数组类型
- AtomicIntegerArray:原子操作更新整数数组
- AtomicLongArray: 原子操作更新长整数数组
- AtomicReferenceArray: 原子操作更新引用对象数组
AtomicIntegerArray源码查看看,跟AtomicInteger方法基本相同,只是数组多个下标操作。按下标查找相应偏移量原子操作更新相应值。 AtomicLongArray和AtomicReferenceArray原子数组操作,其次按类型原子更新。 AtomicLongArray按long类型原子操作,AtomicReferenceArray 按Object类型原子操作。
属性原子修改器类型
- AtomicIntegerFieldUpdater:原子操作修改int类型属性
- AtomicLongFieldUpdater:原子操作修改long类型属性
- AtomicReferenceFieldUpdater:原子操作修改引用类型属性
属性原子修改器类型通过反射原子更新对象的字段,既然他们的作用是更新字段我们知道有些类型的字段是不可被更新的,所以被更新的 字段是有一定的要求:
- 必须是volatile类型(volatile是线程可见变量,保存在Jvm的主内存中,而不是线程的工作内存里面)
- 只能是实例变量,不能是类变量,也就是说不能加static关键字;
- 只能是可修改变量,不能使final变量,因为final的语义就是不可修改。实际上final的语义和volatile是有冲突的,这两个关键 字不能同时存在,
- 对于AtomicIntegerFieldUpdater和AtomicLongFieldUpdater只能修改int/long类型的字段,不能修改其包装类型(Integer/Long)。 如果要修改包装类型就需要使用AtomicReferenceFieldUpdater。
AtomicIntegerFieldUpdater源码分析,AtomicIntegerFieldUpdater定义了一些抽象方法,跟普通AtomicInteger一样
|
|
AtomicLongFieldUpdater这块有不同的就是要判断一下硬件底层是否支持longCAS指令。
|
|
AtomicReferenceFieldUpdater这块AtomicReference很类似,没主体讲解。
解决ABA类型
ABA问题:假设两个线程T1和T2访问同一个变量V,当T1访问变量V时,读取到V的值为A;此时线程T1被抢占了,T2开始执行,T2先将变量 V的值从A变成了B,然后又将变量V从B变回了A;此时T1又抢占了主动权,继续执行,它发现变量V的值还是A,以为没有发生变化,但是实际 上却变化了。这个过程中,变量V从A变为B,再由B变为A就被形象地称为ABA问题了。
解决ABA问题实现两个类如下:
- AtomicMarkableReference:[reference, boolean] pairs.对象原子操作,用布尔类型标记解决ABA问题
|
|
- AtomicStampedReference:[reference, integer] pairs.对象原子操作,用整数标记解决ABA问题。
|
|