Java为何能将读与写封装为一个原子操作?

文章前记

        程序员工作久了便可能整日忙碌于“增删改查”中,迷失方向,毫无进步。

        该公众号致力于分享软件开发相关的原创干货,助你完成从程序员到架构师的进阶之路!

        努力!做一个NB的Coder!



1 背景

在编程中,我们知道如果对一个变量读取然后写入这并不是一个原子操作,例如我们常用的i++操作,其实可以简单划分为以下三步:

以上三步操作时独立的,因此并不是原子化的。如果i变量在第1步和第3步之间被其他线程更改则会引发意向不到的结果。例如线程1对i=5进行i++操作时,线程2对变量i进行了写入操作,则还会发生混乱。如图所示:

此时我们看到,因为i++操作不是原子的,中间受到线程2的影响,使得i++的操作是错误的。

如果是原子化操作,则i++操作要么发生在线程2的操作之前,最终得到i=3;要么发生在线程2操作之后,得到i=4。而不能出现i=6这样的结果。

其实i++就是一个最简单的汇聚了读取和写入的操作,因此读取并写入操作并不是原子化的。可是,Java中确实能够将读取并写入操作封装为一个原子化操作。那它是怎样实现的呢?

我们先介绍一种粗暴的实现:使用同步块实现原子化。

使用同步块实现原子化

为了能实现i++操作的原子化,则最容易想到的就是使用同步块:

但这显然大材小用,将整个操作放置在同步块中,虽然借助同步块实现了原子化,但因为同步块的操作要进行锁的分配等,使得程序的整体效率受到了影响。

而我们知道Java中AtomicInteger类可以直接将读和写封装在一个原子操作中实现原子化的i++等操作,那它们是怎么实现的呢?我们接下来进行探讨。

3 硬件CAS操作

Java中AtomicInteger类便可以将读和写封装在一个原子操作中。那它是怎么实现的呢?

它使用的是CAS操作。CAS是compare and swap的缩写,中文可以翻译成:比较并交换。CAS操作来源于底层硬件领域。因为CAS能够极大地提高并发效率,因此在硬件设计领域,CAS这种操作就是存在的。例如在intel的CPU中,使用cmpxchg指令就可以实现CAS操作。我们详细介绍下CAS操作的原理:

CAS操作一共包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。具体操作过程如下:

1. 首先,返回V位置的原值R

 2. 如果内存位置的值R与预期原值A相匹配,那么处理器会自动将该位置值更新为新值

  3. 否则,处理器不做任何操作

无论结果如何,该操作总能拿到V处的原值R,如果R等于A,则说明V处的值已经被更新为了B;否则说明更新失败,V处的值依旧是R。因此,经过该操作后,可以知道CAS操作是否成功。

当然,还有一种CAS实现是直接返回操作是否成功的结果,即:如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值,并返回操作成功;否则,返回操作失败。

但是这与返回V位置原值的方式大同小异,而且返回原值R的实现方式不仅能获得操作是否成功的结果,还能顺便获得R值,因此更为常用。

Java中的CAS操作

在Java初期,java是无法直接利用这些硬件操作来提升并发性能的,直到后来Java本地方法(JNI)的出现,使得Java直接调用本地方法称为可能,在Doug Lea提供的cucurenct包得以实现。

在Java中如何实现呢?其实知道了CAS操作的原理,一下就变得简单起来。我们还是以i++操作为例,介绍CAS整个过程:

1. 读取内存位置V的原值R

2. 因为进行的是i++操作,因此CAS操作的预期原值A=R、新值B=R+1,进行CAS。

3. 获取V的原值R

4. 如果R等于A,更改成功,操作结束

5. 如果R不等于A,则没有更改。但是获取当前时刻V的原值,将R重新设置为该值。则预期原值A=R、新值B=R+1,然后在继续进行CVS。

空口无凭,我们看一下AtomicInteger中i++操作的实现方法getAndIncrement():

而getAndAddInt()方法如下:

我们继续追踪到compareAndSwapInt()方法,分析该方法是一个本地方法。

稍加需要注意的是,可能由于其他原因,这里的compareAndSet采用的是返回操作是否成功的实现方式,而不是返回当前内存位置原值R的方式。因此在每一次进行CAS操作时,都需要先调用getIntVolatile(var1, var2)方法获取一次当前的变量值作为预期原值A。

因此,getAndIncrement()的具体操作就是:

获取当前值,在此基础上+1作为目标值;进行CAS操作;如果不成功,说明值不是最新的。重新获取值,不断再试。

如此一来,我们就知道了Java中是如何将读取和写入这两个操作封装成一个原子操作的。

总结

CAS的思想本质上就是乐观锁思想。即:假设情况都是乐观的,那么当前内存位置V的值是预期原值A,如果这样,请把内存位置V的值设置为新值B;否则,乐观的情况不成立,什么也别做。

乐观锁的思想减少了使用同步块等悲观锁的开销,但是本质是基于乐观锁思想的,因此肯定存在一些问题。

对与CAS引发的问题与解决办法,我们将会在下次文章中详细介绍。

End


欢迎关注我们,不错过每期的原创干货!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 原创文章&经验总结&从校招到A厂一路阳光一路沧桑 详情请戳www.codercc.com 1. 原子操作类介绍 在...
    你听___阅读 21,378评论 3 24
  • 本文是我自己在秋招复习时的读书笔记,整理的知识点,也是为了防止忘记,尊重劳动成果,转载注明出处哦!如果你也喜欢,那...
    波波波先森阅读 11,319评论 4 56
  • 据报纸记载,龙头寺是因为过去就有做寺庙叫龙头寺,距今也有些历史了。现在说龙头寺绝不是仅仅指寺庙,而是已经演化为泛指...
    木樂阅读 557评论 0 0
  • 2017年11月17日下午,外婆终于没能抵抗住病魔的折磨,永远地走了。 85岁的外婆一生勤劳,养育了6个孩子,还帮...
    蝶舞心间阅读 399评论 0 0
  • 对,没错,快要过年了,在回家的路上来和大家说说过年,回家…… 有家才有年,过年才回家。每一次过年,每一次回家,每个...
    書寫心思阅读 215评论 1 1