简述
在Java中我们要保证一个共享变量在多线程环境下的安全问题,最容易想到的就是用锁进行操作的同步,但是用锁的话对性能有非常大的影响,于是Java提供了许多基于CAS的原子操作类来提高性能。
原子变量操作类
JUC 并发包中包含有AtomicInteger、AtomicLong 和 AtomicBoolean等原子性操作类,它们的原理类似。我们通过学习AtomicLong来一举反三即可,原子变量操作类其内部使用Unsafe来实现。
public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;
//获得Unsafe实例,因为AtomicLong是由启动类加载器加载的,所以可以直接拿到Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
//存放变量value的偏移量
private static final long valueOffset;
//判断JVM是否支持Long类型的CAS操作
static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
private static native boolean VMSupportsCS8();
static {
try {
//拿到AtomicLong中value的偏移量
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
//实际的变量值
private volatile long value;
递增和递减操作
//原子性递增
public final long getAndIncrement() {
return unsafe.getAndAddLong(this, valueOffset, 1L);
}
//原子性递减
public final long getAndDecrement() {
return unsafe.getAndAddLong(this, valueOffset, -1L);
}
这里可以看到原子性递增和递减操作都是通过Unsafe的getAndAddLong方法来实现的,这个是jdk 1.8以及之后的实现,而在jdk 1.7的时候,getAndIncrement的实现逻辑为:
public final long getAndIncrement ( ) {
// 自旋
while (true){
// 获得旧值
long current = get () ;
// 把旧值加一
long next = current + l;
// 通过CAS操作用新值替代旧值,compareAndSet内会调用Unsafe的compareAndSwapLong方法
if (compareAndSet (current,next) )
return current;
}
}
在JDK1.8的时候getAndIncrement直接就调用了unsafe.getAndAddLong,其实getAndAddLong中的实现与JDK 1.7中getAndIncrement实现原理一样,只是把这一部分代码放到了Unsafe中。
compareAndSet
public final boolean compareAndSet(long expect, long update) {
return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
compareAndSet内部还是调用了unsafe.compareAndSwapLong方法。如果原子变量中的value值等于expect,则使用update值更新该值并返回true,否则返回false。
原子操作类的问题
在我们上面对代码的分析中可以看到,其实原子操作类通过CAS+自旋的方式实现操作在多线程下的正确性,但是也存在性能的问题,在多线程下,只有一个线程可以完成值变化的操作,其他线程都要进行自旋重新操作,如果操作一直没有成功,就要一直发生自旋,这样就大量的消耗了CPU的资源。
JDK8新增的LongAdder
AtomicLong存在的问题是因为CAS操作只有一个线程会成功,其他线程的操作失败的话需要进行自旋,而多个线程对一个value进行操作失败的概率就会大大的提升,这样就白白浪费了CPU的资源。
JDK8新增了一个原子性递增或者递减类LongAdder用来克服在高并发下使用AtomicLong的缺点。既然AtomicLong 的性能瓶颈是由于过多线程同时去竞争一个变量的更新而产生的,那么如果把一个变量分解为多个变量,让同样多的线程去竞争多个资源,这样就解决了性能问题。
在LongAdder中值不是简单的value一个变量记录,而是由base和若干个cell记录,value = base + … + cell。这样把多线程的操作分散去操作多个Cell,可以减少冲突,从而提高性能。Cell结构如下:
@sun.misc.Contended static final class Cell {
volatile long value;
Cell(long x) { value = x; }
final boolean cas(long cmp, long val) {
// cas
return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
}
// unsafe实例
private static final sun.misc.Unsafe UNSAFE;
// value属性的内存偏移量
private static final long valueOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> ak = Cell.class;
valueOffset = UNSAFE.objectFieldOffset
(ak.getDeclaredField("value"));
} catch (Exception e) {
throw new Error(e);
}
}
}
这里可以看到Cell用了@sun.misc.Contended注解,这个注解是用来防止伪共享的,一个Cell一个缓冲行(常见的缓存行大小为 64 字节)。