Java企业教程系列

Java 8 LongAdders

  Java 8带来了新的并行加法器。这是一组新的类,用于管理多个线程读写的计数器。新的API有显著的性能提升,同时仍保持简单明了。

随着人们使用多核架构来实现并发编程,让我们首先比较一下Java则提供的一些并发控制API并进行比较一下。

脏计数器Dirty counter

这种方法意味着你可以跨多个线程对一个对象或静态字段进行读写。不幸的是,有两个原因使其并不能正常工作。第一个是在Java中“A+ = B”这样的操作不是原子。如果你打开Java编译其输出的字节码,你会看到至少四条指令 - 一个用于从堆heap中加载到线程堆栈thread stack的字段值,第二个用于装载增量,第三个添加它们以及第四条设定结果进入字段。

如果一个以上的线程在同一时间对相同的内存位置执行此操作,在进行同时写操作发生错误的机会很高,因为一个线程可以覆盖另一个值(又名"读 - 修改 - 写" ) 。

还有另外一个讨厌的地方是这里的值得做volatility。详见下文。这是个新手容易忽视的错误,另一个问题是超级难调试。

同步Synchronized

这是最基本的并发用语,这是阻止所有其他线程同时读取或写入。这会带来多线程变成单线程的极大性能隐患。

读写锁RWLock

稍微复杂一点Java锁版本,读写锁让你辨别读写操作,如果是读操作,就不需要阻止别的线程同时读,如果是写操作,那么再如同步锁一样阻止别的线程。虽然这可能是更有效率的(假设写操作的数量低),但是因为你得到写锁以后还是要阻止其他线程操作,这实际和同步锁没什么两样。

Volatile

很多人误解这个关键字主要指示JIT编译器去优化运行时机器代码,这样任何修改能够立即让其他线程看到。但是这个可能被JIT编译器最喜欢的优化破坏变得无效,JIT编译器可能会改变其中分配给字段的顺序,这样能够最大限度地减少程序需要访问全局堆的次数,同时还确保你的代码是不受其影响。相当鬼祟......

所以,那么我们是否应该使用volatile计数器?如果您只有一个线程更新一个值,多个线程读取消费它,这是一个非常好的策略 。

那么什么时候不去使用它呢?当超过一个以上线程去更新字段值时,最好不去使用它,因为 A += B并不是原子的,你可以使用AtomicInteger

AtomicInteger

这是使用处理器的CAS (compare-and-swap)来进行计数器更新,因为它采用了直接的机器代码指令,这样讲多线程执行的影响降低到最小。缺点是,如果失败,因为与另一个线程进行竞争会设置值发生失败,只有再试一次。在高度竞争高并发情况下这可能变成一个自旋锁,线程在一个无限循环必须不断地尝试和设置的值,直到成功为止。这是不太正是我们所期待的。

Java 8 Adders

从用户角度它非常类似AtomicInteger. 只是创建了一个LongAdder然后使用intValue() 和 add() 来获取或设置值,魔术发生在背后。

当直接的CAS发生争夺失败时,这个类将增量存储在一个内部的单元对象中,这个对象是为线程分配的,当intValue() 被调用时它然后增加pending单元的值 ,这会降低返回和CAS或堵塞其他线程的情况发生。

下面是上面几种并发累计的性能比较:

测试代码见Github

LongAdder有近60-100%的性能提升。

Java 8的StampedLocks

并发主题