java中Atomic变量的实现原理是怎么样的( 四 )


使用AtomicLong时,在高并发下大量线程会同时去竞争更新同一个原子变量,但是由于同时只有一个线程的CAS会成功,所以其他线程会不断尝试自旋尝试CAS操作,这会浪费不少的CPU资源。而LongAdder可以概括成这样:内部核心数据value分离成一个数组(Cell),每个线程访问时,通过哈希等算法映射到其中一个数字进行计数,而最终的计数结果,则为这个数组的求和累加。简单来说就是将一个值分散成多个值,在并发的时候就可以分散压力,性能有所提高。
本已收录至我的GitHub精选文章,欢迎Star:https://github.com/ZhongFuCheng3y/3y求点赞 求关注? 求分享 求留言 对我来说真的 非常有用!!!收藏等于白嫖,点赞才是真情!收藏等于白嫖,点赞才是真情!收藏等于白嫖,点赞才是真情!
■网友
这个问题我刚好研究过,还写了一篇文章:
Alex Wang:Java Concurrency代码实例之三原子变量

■网友
AtomicIntegerAtomicInteger中的incrementAndGet方法就是乐观锁的一个实现,使用自旋(循环检测更新)的方式来更新内存中的值并通过底层CPU执行来保证是更新操作是原子操作。方法如下:
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); //可以看做compareAndSwapInt(obj, offset, expect, update) return var5;}首先这个方法通过getIntVolatile方法,使用对象的引用与值的偏移量得到当前值,然后调用compareAndSwapInt检测如果obj内的value和expect相等,就证明没有其他线程改变过这个变量,那么就更新它为update,如果这一步的CAS没有成功,那就采用自旋的方式继续进行CAS操作。
在赋值的时候保证原子操作的原理是通过CPU的cmpxchgl与lock指令的支持来实现AtomicInteger的CAS操作一定程度上的原子性,具体可参考这里,https://juejin.im/post/5a73cbbff265da4e807783f5
疑问:这个方法是先得到值,再更新值,所以必须保证更新的值是在原来的基础上更新的,所以采用CAS进行更新,那么为什么不使用直接更新值然后返回值的方式来做呢?因为更新值的前提是获取值,这是两部汇编级别的操作,仅仅更新值是无法获取到值的。
ABA问题上面提到过lock指令,它能保证其他CPU无法参与进来,但是无法保证单个CPU的另一个线程执行更新操作。所以如果一个值原来是A,变成了B,又变成了A,那么使用CAS进行检查时会发现它的值没有发生变化,但是实际上却变化了。这就是CAS的ABA问题。
常见的解决思路是使用版本号。在变量前面追加上版本号,每次变量更新的时候把版本号加一,那么A-B-A 就会变成1A-2B-3A。
AtomicStampedReference来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
循环时间长开销大问题上面我们说过如果CAS不成功,则会原地自旋,如果长时间自旋会给CPU带来非常大的执行开销。

来源:http://www.wangtianyi.top/blog/2018/04/28/javagao-bing-fa-xi-lie-er-:xian-cheng-an-quan-xing/

■网友
去看API会发现调用了sun的Unsafe类,顾名思义,Unsafe类中包含了很多Java认为不安全的操作,比如内存的直接分配等,Unsafe类还有一个compareAndSwapXX方法,它会调用底层的native方法,而这个native方法就是由现代大多数CPU都支持的CAS(Compare And Swap,比较并交换)指令提供硬件支持。典型的CAS操作有三个操作值:期望值:e,新值:n,以及内存中的实际值:x。首先检查e和x是否相等,如果不相等则线程自旋等待,如果相等则设置并返回。具体参加java.util.atomic。


推荐阅读