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小强
未曾清贫难成人,不经打击老天真。
自古英雄出炼狱,从来富贵入凡尘。
发表评论: