在多线程编程中,有着三个至关重要的特性,分别是原子性,可见性,有序性。本文是学习了多线程后对于多线程中原子性的知识点的一些记录。
首先,我们先来了解下什么是原子性:原子性即是指在一个或多个操作中,要么所有的操作都执行成功并且不会受到任何因素的干扰而中断,要么都不执行,不管是一个操作或者多个操作,都是最小的执行单位,不能分割。
用银行转账来说明原子操作:James从自己的账户中往tony的账户中转了100元,这个过程包含了两个基本的操作,首先,从James的账户中减去100元,然后,tony的账户中增加100元。这两个操作必须是一体的,要么都执行成功,要么都不执行,不能James的账户减了100元,而Tony的账户不增加,或者James的账户不减,但tony的账户却增加了。所以,这个转账的过程,必须要符合原子性。而资源在整个操作中,保持一致,没有多,也没有少,这就是原子性的核心特征。
再看两条java语句:
1:int x = 100;
2:x++;
在Java内存模型(JMM)中,定义了8种原子操作,lock(锁定),unlock(解锁),read(读取),write(写入),load(载入),store(存储),use(使用),assign(赋值)。基本的读取写入和赋值等是保证了原子性的,其他的操作均不能保证,即第一个赋值语句,是原子操作。但是在第二个语句中,x自增,并不是一个原子操作,它包含了三个步骤,第一步:获取x的值;第二步:+1;第三步:重新赋值给x。这三个步骤,每一步单独都是原子操作,但是合到一起,就不是一个原子操作。
若在单线程中,x++是不是原子性无关重要,但是在多线程中,若x++不是一个原子操作,那得到的结果百分之九十九不是我们希望的。那么怎么在多线程中实现类似 x++操作的原子性呢?有两种方式,一是加锁,使用synchronized关键字或者使用 JUC 中的lock。二是使用JUC中的atomic包中的原子操作类。
加锁的方式,相信大家都比较了解,不再讲述,本文主要了解下atomic包中的原子操作类。
打开 源码,在JUC中的atomic包中,我们能看到一系列以 Atomic 开头的类,有AtomicBoolean,AtomicInteger等原子基本类型操作类,也有AtomicIntegerArray,AtomicReference等原子数组,原子引用类型操作类。这些类都是封装好的原子操作类。
为什么说这些类实现了原子操作?在这里,我们先说一个概念:CAS机制
CAS:Compare And Swap,比较和交换。这是一个硬件同步用语,是处理器提供基本内存操作的原子性保证。
CAS机制需要三个变量,一个是内存地址,一个是旧值,一个是新值。只要当内存地址中的数值与旧值相等时,才会将内存地址中对应的值修改成新值,如果不相等,则更新失败。
更新失败后,重新获取旧值,然后再比较,再更新,直到成功,这个过程称为自旋。
自旋 + CAS,是保证原子性的一种方式。java中的 sun.misc.Unsafe类,提供了 compareAndSwapInt()和compareAndSwapLong()等几个实现了 自旋+CAS 的方法。而 Atomic*类则是调用了Unsafe类中的方法来实现的。所以说Atomic*类能保证原子性。
相比于加锁,CAS是一种轻量级的实现,所以推荐使用Atomic*类,但它也有自己的不足:
1.自旋+CAS,自旋的实现让所有的线程都处于高频运行,争抢CPU执行时间的状态,如果操作长时间不成功,会带来很大的CPU资源消耗;
2.CAS仅针对单个变量的操作实现原子性,不能用于多个变量;
3.ABA问题。
前两个不足很好理解,关键是第三个问题:ABA问题。
假设有两个线程,同时操作位置 V 中的 数据 A。
线程1和线程2都同时读取到了 A ,然后线程1都要执行CAS(V,A,B)操作,把旧值A修改给新值B。
假设线程2操作落后于线程1,线程1成功执行后,结果又执行了另一个操作,又将B修改成了原来的A,此时,轮到线程2来执行了,线程2并不知道之前的操作,看到地址V中的值仍然是A,与自己的旧值相等,就兴高采烈的将旧值替换了新值了,然后线程2的CAS操作也执行成功了,虽然都执行成了,但结果却并不是我们希望的,线程1替换之前的A虽然和替换后的A相等,但毕竟不是同一个A,就像本山大叔说的,你大妈已经不是你大妈了。
如何解决这个问题呢?很简单,加个版本号,替换之前A的版本是V1,替换为B后,版本为V2,再修改为A后,版本为V3,线程2在执行时,除了比较旧值是否相等,还会比较版本是否一致,此时,线程2并不会执行成功。
在Atomic*类中,AtomicStampedReference类是实现了带有版本号的CAS操作。
本文是对多线程原子性的一个粗略的讲解,后续有更深入的了解后,还会进行更新,有不对的地方,请斧正。