Java小强个人技术博客站点    手机版
当前位置: 首页 >> 理论 >> 并发编程之AtomicInteger,AtomicLong,LongAdder

并发编程之AtomicInteger,AtomicLong,LongAdder

3860 理论 | 2022-9-27

AtomicInteger类是系统底层保护的int类型,通过提供执行方法的控制进行值的原子操作。

AtomicInteger它不能当作Integer来使用。AtomicInteger与使用同步执行相同操作相比,使用它同样更快,更易读。

在 JDK1.5 中新增了并发情况下使用的 Integer/Long 所对应的原子类AtomicInteger 和 AtomicLong。

在并发的场景下,如果我们需要实现计数器,可以利用AtomicInteger和AtomicLong,这样一来,就可以避免加锁和复杂的代码逻辑,有了它们之后,我们只需要执行对应的封装好的方法,例如对这两个变量进行原子的增操作或原子的减操作。


AtomicInteger通过调用构造函数可以直接创建。在AtomicInteger提供了两种方法来获取和设置它的实例的值。

在现实生活中,我们需要AtomicInteger两种情况:

1、作为多个线程同时使用的原子计数器。

2、在比较和交换操作中实现非阻塞算法。


AtomicInteger作为原子计数器

@Test
public void AtomicIntegerTest1(){
    //初始值是 0
    AtomicInteger atomicInteger = new AtomicInteger(100);
    // 以原子方式将给定值添加到当前值,并在添加后返回新值。
    System.out.println(atomicInteger.addAndGet(2));    //102
    System.out.println(atomicInteger);                      //102

    // 以原子方式将给定值添加到当前值并返回旧值。
    System.out.println(atomicInteger.getAndAdd(2));    //102
    System.out.println(atomicInteger);                      //104

    // 以原子方式将当前值递增1并在递增后返回新值。它相当于i ++操作。
    System.out.println(atomicInteger.incrementAndGet());    //105
    System.out.println(atomicInteger);                      //105

    // 以原子方式递增当前值并返回旧值。它相当于++ i操作。
    System.out.println(atomicInteger.getAndIncrement());    //105
    System.out.println(atomicInteger);                      //106

    // 原子地将当前值减1并在减量后返回新值。它等同于i-操作。
    System.out.println(atomicInteger.decrementAndGet());    //105
    System.out.println(atomicInteger);                      //105

    // 以原子方式递减当前值并返回旧值。它相当于-i操作。
    System.out.println(atomicInteger.getAndDecrement());    //105
    System.out.println(atomicInteger);                      //104
}


比较和交换操作

1、比较和交换操作将内存位置的内容与给定值进行比较,并且只有它们相同时,才将该内存位置的内容修改为给定的新值。这是作为单个原子操作完成的。

2、原子性保证了新值是根据最新信息计算出来的; 如果在此期间该值已被另一个线程更新,则写入将失败。

@Test
public void AtomicIntegerTest2(){
    AtomicInteger atomicInteger = new AtomicInteger(100);

    //默认初始值和给定值,都是100,所以会更改成功
    boolean isSuccess = atomicInteger.compareAndSet(100,110);   //current value 100
    System.out.println(isSuccess);      //true

    //上面代码已经改为110,给定值是100,所以会更改失败
    isSuccess = atomicInteger.compareAndSet(100,120);       //current value 110
    System.out.println(isSuccess);      //false
}


AtomicLong和AtomicInteger是一样的,只是类型不同。在 JDK 8 中又新增了 LongAdder 这个类,这是一个针对 Long 类型的操作工具类。


那么既然已经有了 AtomicLong,为何又要新增 LongAdder 这么一个类呢?

对于AtomicLong内部的value属性而言,也就是保存当前AtomicLong数值的属性,它是被volatile修饰的,所以它需要保证自身可见性。

这样一来,每一次它的数值有变化的时候,它都需要进行flush和refresh。由于竞争很激烈,这样的flush和refresh操作耗费了很多资源,而且CAS也会经常失败。

但是 AtomicLong 依然可以保证 incrementAndGet 操作的原子性,所以不会发生线程安全问题。


为什么高并发下LongAdder比AtomicLong效率更高?

因为LongAdder引入了分段累加的概念,内部一共有两个参数参与计数:第一个叫作base,它是一个变量,第二个是Cell[],是一个数组。

其中的base是用在竞争不激烈的情况下的,可以直接把累加结果改到base变量上。

那么,当竞争激烈的时候,就要用到我们的Cell[]数组了。一旦竞争激烈,各个线程会分散累加到自己所对应的那个Cell[]数组的某一个对象中,而不会大家共用同一个。

这样一来,LongAdder会把不同线程对应到不同的Cell上进行修改,降低了冲突的概率,这是一种分段的理念,提高了并发性,这就和Java7的ConcurrentHashMap的16个Segment的思想类似。

竞争激烈的时候,LongAdder会通过计算出每个线程的hash值来给线程分配到不同的Cell上去,每个Cell相当于是一个独立的计数器,这样一来就不会和其他的计数器干扰,Cell之间并不存在竞争关系,所以在自加的过程中,就大大减少了刚才的flush和refresh,以及降低了冲突的概率,因为它有多个计数器同时在工作,所以占用的内存也要相对更大一些。

那么LongAdder最终是如何实现多线程计数的呢?答案就在最后一步的求和sum方法,执行LongAdder.sum()的时候,会把各个线程里的Cell累计求和,并加上base,形成最终的总和。

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;
}

在这个sum方法中可以看到,思路非常清晰。先取base的值,然后遍历所有Cell,把每个Cell的值都加上去,形成最终的总和。由于在统计的时候并没有进行加锁操作,所以这里得出的sum不一定是完全准确的,因为有可能在计算sum的过程中Cell的值被修改了。


在低竞争的情况下,AtomicLong和LongAdder这两个类具有相似的特征,吞吐量也是相似的,因为竞争不高。但是在竞争激烈的情况下,LongAdder的预期吞吐量要高得多,经过试验,LongAdder的吞吐量大约是AtomicLong的十倍,不过凡事总要付出代价,LongAdder在保证高效的同时,也需要消耗更多的空间


AtomicLong可否被LongAdder替代?

那么我们就要考虑了,有了更高效的LongAdder,那AtomicLong可否不使用了呢?是否凡是用到AtomicLong的地方,都可以用LongAdder替换掉呢?

答案是不是的,这需要区分场景。

LongAdder只提供了add、increment等简单的方法,适合的是统计求和计数的场景,场景比较单一,而AtomicLong还具有compareAndSet等高级方法,可以应对除了加减之外的更复杂的需要CAS的场景。

推荐您阅读更多有关于“ 多线程 并发编程 AtomicInteger LongAdder AtomicLong ”的文章

上一篇:并发编程之Java中Selector 下一篇:多线程同步计数器CountDownLatch,CyclicBarrier,Semaphore

猜你喜欢

发表评论: