Java并发-原子操作类

简述

在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 字节)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值