Java8新特性系列(原子性操作)

本文对比分析了Java8中的AtomicLong和LongAdder两种实现原子操作的方法。介绍了这两种类的基本用法,并深入探讨了它们的工作原理,特别是在高并发场景下的表现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

点击上方“中兴开发者社区”,关注我们

每天读一篇一线开发者原创好文

上期我们介绍了Java8中新的时间日期API:码上论剑|Java8新特性系列(时间/日期),本期我们介绍Java8中原子性操作LongAdder

原子操作

根据百度百科的定义:

“原子操作(atomic operation)是不需要synchronized”,这是Java多线程编程的老生常谈了。所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)。

AtomicLong

在单线程的环境中,使用Long,如果对于多线程的环境,如果使用Long的话,需要加上synchronized关键字,从Java5开始,JDK提供了AtomicLong类,AtomicLong是一个提供原子操作的Long类,通过线程安全的方式操作加减,AtomicLong提供原子操作来进行Long的使用,因此十分适合高并发情况下的使用。

public class AtomicLongFeature {    
   private static final int NUM_INC = 1_000_000;    
   private static AtomicLong atomicLong = new AtomicLong(0);    
   private static void update() {        atomicLong.set(0);        ExecutorService executorService = Executors.newFixedThreadPool(5);        IntStream.range(0, NUM_INC).forEach(i -> {            Runnable task = () -> atomicLong.updateAndGet(n -> n + 2);            executorService.submit(task);        });        stop(executorService);        System.out.println(atomicLong.get());    }    
   private static void stop(ExecutorService executorService) {        
       try {            executorService.shutdown();            executorService.awaitTermination(60, TimeUnit.SECONDS);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            
           if (!executorService.isTerminated()) {                System.out.println("kill tasks");            }            executorService.shutdownNow();        }    }    
   public static void main(String[] args) {        update();    } }

输出:
2000000

为什么AtomicInteger能支持高并发呢?看下AtomicLongupdateAndGet方法:

public final int updateAndGet(IntUnaryOperator updateFunction) {    
   int prev, next;    do {        prev = get();        next = updateFunction.applyAsInt(prev);    } while (!compareAndSet(prev, next));    return next; }
public final boolean compareAndSet(int expect, int update) {    
   return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }

原因是每次updateAndGet时都会调用compareAndSet方法。

AtomicLong是在使用非阻塞算法实现并发控制,在一些高并发程序中非常适合,但并不能每一种场景都适合,不同场景要使用使用不同的数值类。

LongAdder

AtomicLong的原理是依靠底层的cas来保障原子性的更新数据,在要添加或者减少的时候,会使用死循环不断地cas到特定的值,从而达到更新数据的目的。那么LongAdder又是使用到了什么原理?难道有比cas更加快速的方式?

public class LongAdderFeature {    
   private static final int NUM_INC = 1_000_000;    
   private static LongAdder longAdder = new LongAdder();    
   private static void update() {        ExecutorService executorService = Executors.newFixedThreadPool(5);        IntStream.range(0, NUM_INC).forEach(i -> {            Runnable task = () -> longAdder.add(2);            executorService.submit(task);        });        stop(executorService);        System.out.println(longAdder.sum());    }    
   private static void stop(ExecutorService executorService) {        
       try {            executorService.shutdown();            executorService.awaitTermination(60, TimeUnit.SECONDS);        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            
           if (!executorService.isTerminated()) {                System.out.println("kill tasks");            }            executorService.shutdownNow();        }    }    
   public static void main(String[] args) {        update();    } }

输出:
2000000

我们来看下LongAdder的add方法:

public void add(long x) {
    Cell[] as; 
   long b, v;
   int m;
   Cell a;    
   if ((as = cells) != null || !casBase(b = base, b + x)) {        
       boolean uncontended = true;        
       if (as == null || (m = as.length - 1) < 0 ||            (a = as[getProbe() & m]) == null ||            !(uncontended = a.cas(v = a.value, v + x)))            longAccumulate(x, null, uncontended);    } }

我们可以看到一个Cell的类,那这个类是用来干什么的呢?

@sun.misc.Contended static final class Cell {    
   volatile long value;    Cell(long x) { value = x; }    
   final boolean cas(long cmp, long val) {        
       return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);    }    // Unsafe mechanics    private static final sun.misc.Unsafe UNSAFE;    
   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类的内部是一个volatile的变量,然后更改这个变量唯一的方式通过cas。我们可以猜测到LongAdder的高明之处可能在于将之前单个节点的并发分散到各个节点的,这样从而提高在高并发时候的效率。

LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。

public long sum() {
    Cell[] as = cells; Cell a;    
   long sum = base;    
   if (as != null) {        
       for (int i = 0; i < as.length; ++i) {            
           if ((a = as[i]) != null)                sum += a.value;        }    }    
   return sum; }

当计数的时候,将base和各个cell元素里面的值进行叠加,从而得到计算总数的目的。这里的问题是在计数的同时如果修改cell元素,有可能导致计数的结果不准确,所以缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值